From 9038ab3921293a60bfe3200fed1aad241df0acb0 Mon Sep 17 00:00:00 2001 From: Tpt Date: Thu, 30 Jul 2020 16:41:34 +0200 Subject: [PATCH] Converts oxigraph::Error into oxigraph::sparql::EvaluationError --- lib/src/error.rs | 192 +++------------- lib/src/io/read.rs | 73 +++--- lib/src/io/write.rs | 8 +- lib/src/lib.rs | 8 +- lib/src/model/blank_node.rs | 11 +- lib/src/model/named_node.rs | 5 +- lib/src/sparql/error.rs | 109 +++++++++ lib/src/sparql/eval.rs | 105 +++++---- lib/src/sparql/json_results.rs | 16 +- lib/src/sparql/mod.rs | 114 +++++---- lib/src/sparql/model.rs | 65 ++++-- lib/src/sparql/parser.rs | 41 ++-- lib/src/sparql/plan.rs | 12 +- lib/src/sparql/plan_builder.rs | 46 ++-- lib/src/sparql/xml_results.rs | 371 +++++++++++++++++++----------- lib/src/store/memory.rs | 35 ++- lib/src/store/mod.rs | 7 +- lib/src/store/numeric_encoder.rs | 6 +- lib/src/store/rocksdb.rs | 26 +-- lib/src/store/sled.rs | 30 +-- python/src/memory_store.rs | 2 +- python/src/sled_store.rs | 2 +- python/src/store_utils.rs | 19 +- testsuite/src/sparql_evaluator.rs | 21 +- 24 files changed, 742 insertions(+), 582 deletions(-) create mode 100644 lib/src/sparql/error.rs diff --git a/lib/src/error.rs b/lib/src/error.rs index 102dfa8f..c364c232 100644 --- a/lib/src/error.rs +++ b/lib/src/error.rs @@ -1,171 +1,37 @@ -use crate::model::{BlankNodeIdParseError, IriParseError, LanguageTagParseError}; -use crate::sparql::SparqlParseError; -use rio_turtle::TurtleError; -use rio_xml::RdfXmlError; -use std::error; -use std::fmt; -use std::io; -use std::string::FromUtf8Error; - -/// The Oxigraph error type. -/// -/// The `wrap` method allows us to make this type wrap any implementation of `std::error::Error`. -/// This type also avoids heap allocations for the most common cases of Oxigraph errors. -#[derive(Debug)] -pub struct Error { - inner: ErrorKind, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.inner { - ErrorKind::Msg { msg } => write!(f, "{}", msg), - ErrorKind::Io(e) => e.fmt(f), - ErrorKind::FromUtf8(e) => e.fmt(f), - ErrorKind::Iri(e) => e.fmt(f), - ErrorKind::BlankNode(e) => e.fmt(f), - ErrorKind::LanguageTag(e) => e.fmt(f), - ErrorKind::Other(e) => e.fmt(f), - } - } -} - -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match &self.inner { - ErrorKind::Msg { .. } => None, - ErrorKind::Io(e) => Some(e), - ErrorKind::FromUtf8(e) => Some(e), - ErrorKind::Iri(e) => Some(e), - ErrorKind::BlankNode(e) => Some(e), - ErrorKind::LanguageTag(e) => Some(e), - ErrorKind::Other(e) => Some(e.as_ref()), - } - } -} - -impl Error { - /// Wraps another error. - pub fn wrap(error: impl error::Error + Send + Sync + 'static) -> Self { - Self { - inner: ErrorKind::Other(Box::new(error)), - } - } - - /// Builds an error from a printable error message. - pub fn msg(msg: impl Into) -> Self { - Self { - inner: ErrorKind::Msg { msg: msg.into() }, - } - } -} - -#[derive(Debug)] -enum ErrorKind { - Msg { msg: String }, - Io(io::Error), - FromUtf8(FromUtf8Error), - Iri(IriParseError), - BlankNode(BlankNodeIdParseError), - LanguageTag(LanguageTagParseError), - Other(Box), -} - -impl From for Error { - fn from(error: Infallible) -> Self { - match error {} - } -} - -impl From for Error { - fn from(error: std::convert::Infallible) -> Self { - match error {} - } -} - -impl From for Error { - fn from(error: io::Error) -> Self { - Self { - inner: ErrorKind::Io(error), - } - } -} - -impl From for Error { - fn from(error: FromUtf8Error) -> Self { - Self { - inner: ErrorKind::FromUtf8(error), - } - } -} - -impl From for Error { - fn from(error: IriParseError) -> Self { - Self { - inner: ErrorKind::Iri(error), - } - } -} - -impl From for Error { - fn from(error: BlankNodeIdParseError) -> Self { - Self { - inner: ErrorKind::BlankNode(error), - } - } -} - -impl From for Error { - fn from(error: LanguageTagParseError) -> Self { - Self { - inner: ErrorKind::LanguageTag(error), - } - } -} - -impl From for Error { - fn from(error: TurtleError) -> Self { - Self::wrap(error) - } -} - -impl From for Error { - fn from(error: RdfXmlError) -> Self { - Self::wrap(error) - } -} - -impl From for Error { - fn from(error: quick_xml::Error) -> Self { - Self::wrap(error) - } -} - -impl From for Error { - fn from(error: SparqlParseError) -> Self { - Self::wrap(error) - } -} +use std::error::Error; +use std::{fmt, io}; //TODO: convert to "!" when "never_type" is going to be stabilized #[allow(clippy::empty_enum)] #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub(crate) enum Infallible {} +impl Error for Infallible {} + impl fmt::Display for Infallible { fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { match *self {} } } -impl std::error::Error for Infallible {} +impl UnwrapInfallible for Result { + type Value = T; -impl From for std::convert::Infallible { - fn from(error: Infallible) -> Self { - match error {} + fn unwrap_infallible(self) -> T { + match self { + Ok(value) => value, + Err(error) => match error {}, + } } } +/// Traits that allows unwrapping only infallible results +pub(crate) trait UnwrapInfallible { + type Value; + + fn unwrap_infallible(self) -> Self::Value; +} + impl From for Infallible { fn from(error: std::convert::Infallible) -> Self { match error {} @@ -178,20 +44,16 @@ impl From for std::io::Error { } } -/// Traits that allows unwrapping only infallible results -pub(crate) trait UnwrapInfallible { - type Value; - - fn unwrap_infallible(self) -> Self::Value; +impl From for std::convert::Infallible { + fn from(error: Infallible) -> Self { + match error {} + } } -impl UnwrapInfallible for Result { - type Value = T; +pub(crate) fn invalid_data_error(error: impl Into>) -> io::Error { + io::Error::new(io::ErrorKind::InvalidData, error) +} - fn unwrap_infallible(self) -> T { - match self { - Ok(value) => value, - Err(error) => match error {}, - } - } +pub(crate) fn invalid_input_error(error: impl Into>) -> io::Error { + io::Error::new(io::ErrorKind::InvalidInput, error) } diff --git a/lib/src/io/read.rs b/lib/src/io/read.rs index 4687edad..89eaa9c7 100644 --- a/lib/src/io/read.rs +++ b/lib/src/io/read.rs @@ -1,5 +1,6 @@ //! Utilities to read RDF graphs and datasets +use crate::error::invalid_data_error; use crate::io::{DatasetFormat, GraphFormat}; use crate::model::*; use oxiri::{Iri, IriParseError}; @@ -8,7 +9,6 @@ use rio_api::parser::{QuadsParser, TriplesParser}; use rio_turtle::{NQuadsParser, NTriplesParser, TriGParser, TurtleParser}; use rio_xml::RdfXmlParser; use std::collections::HashMap; -use std::error::Error; use std::io; use std::io::BufRead; @@ -58,7 +58,7 @@ impl GraphParser { /// ///assert_eq!(triples.len(), 1); ///assert_eq!(triples[0].subject.to_string(), ""); - /// # oxigraph::Result::Ok(()) + /// # Result::<_,Box>::Ok(()) /// ``` pub fn with_base_iri(mut self, base_iri: impl Into) -> Result { self.base_iri = Iri::parse(base_iri.into())?.into_inner(); @@ -71,14 +71,14 @@ impl GraphParser { Ok(TripleReader { mapper: RioMapper::default(), parser: match self.format { - GraphFormat::NTriples => { - TripleReaderKind::NTriples(NTriplesParser::new(reader).map_err(invalid_input)?) - } + GraphFormat::NTriples => TripleReaderKind::NTriples( + NTriplesParser::new(reader).map_err(invalid_data_error)?, + ), GraphFormat::Turtle => TripleReaderKind::Turtle( - TurtleParser::new(reader, &self.base_iri).map_err(invalid_input)?, + TurtleParser::new(reader, &self.base_iri).map_err(invalid_data_error)?, ), GraphFormat::RdfXml => TripleReaderKind::RdfXml( - RdfXmlParser::new(reader, &self.base_iri).map_err(invalid_input)?, + RdfXmlParser::new(reader, &self.base_iri).map_err(invalid_data_error)?, ), }, buffer: Vec::new(), @@ -125,15 +125,24 @@ impl Iterator for TripleReader { } if let Err(error) = match &mut self.parser { - TripleReaderKind::NTriples(parser) => { - Self::read(parser, &mut self.buffer, &mut self.mapper, invalid_data) - } - TripleReaderKind::Turtle(parser) => { - Self::read(parser, &mut self.buffer, &mut self.mapper, invalid_data) - } - TripleReaderKind::RdfXml(parser) => { - Self::read(parser, &mut self.buffer, &mut self.mapper, invalid_data) - } + TripleReaderKind::NTriples(parser) => Self::read( + parser, + &mut self.buffer, + &mut self.mapper, + invalid_data_error, + ), + TripleReaderKind::Turtle(parser) => Self::read( + parser, + &mut self.buffer, + &mut self.mapper, + invalid_data_error, + ), + TripleReaderKind::RdfXml(parser) => Self::read( + parser, + &mut self.buffer, + &mut self.mapper, + invalid_data_error, + ), }? { return Some(Err(error)); } @@ -206,7 +215,7 @@ impl DatasetParser { /// ///assert_eq!(triples.len(), 1); ///assert_eq!(triples[0].subject.to_string(), ""); - /// # oxigraph::Result::Ok(()) + /// # Result::<_,Box>::Ok(()) /// ``` pub fn with_base_iri(mut self, base_iri: impl Into) -> Result { self.base_iri = Iri::parse(base_iri.into())?.into_inner(); @@ -220,10 +229,10 @@ impl DatasetParser { mapper: RioMapper::default(), parser: match self.format { DatasetFormat::NQuads => { - QuadReaderKind::NQuads(NQuadsParser::new(reader).map_err(invalid_input)?) + QuadReaderKind::NQuads(NQuadsParser::new(reader).map_err(invalid_data_error)?) } DatasetFormat::TriG => QuadReaderKind::TriG( - TriGParser::new(reader, &self.base_iri).map_err(invalid_input)?, + TriGParser::new(reader, &self.base_iri).map_err(invalid_data_error)?, ), }, buffer: Vec::new(), @@ -269,12 +278,18 @@ impl Iterator for QuadReader { } if let Err(error) = match &mut self.parser { - QuadReaderKind::NQuads(parser) => { - Self::read(parser, &mut self.buffer, &mut self.mapper, invalid_data) - } - QuadReaderKind::TriG(parser) => { - Self::read(parser, &mut self.buffer, &mut self.mapper, invalid_data) - } + QuadReaderKind::NQuads(parser) => Self::read( + parser, + &mut self.buffer, + &mut self.mapper, + invalid_data_error, + ), + QuadReaderKind::TriG(parser) => Self::read( + parser, + &mut self.buffer, + &mut self.mapper, + invalid_data_error, + ), }? { return Some(Err(error)); } @@ -371,11 +386,3 @@ impl<'a> RioMapper { } } } - -fn invalid_input(error: impl Error + Send + Sync + 'static) -> io::Error { - io::Error::new(io::ErrorKind::InvalidInput, error) -} - -fn invalid_data(error: impl Error + Send + Sync + 'static) -> io::Error { - io::Error::new(io::ErrorKind::InvalidData, error) //TODO: drop -} diff --git a/lib/src/io/write.rs b/lib/src/io/write.rs index 3df0d665..09cedbf0 100644 --- a/lib/src/io/write.rs +++ b/lib/src/io/write.rs @@ -29,7 +29,7 @@ use std::io::Write; /// writer.finish()?; /// ///assert_eq!(buffer.as_slice(), " .\n".as_bytes()); -/// # oxigraph::Result::Ok(()) +/// # Result::<_,Box>::Ok(()) /// ``` #[allow(missing_copy_implementations)] pub struct GraphSerializer { @@ -74,7 +74,7 @@ impl GraphSerializer { /// writer.finish()?; /// ///assert_eq!(buffer.as_slice(), " .\n".as_bytes()); -/// # oxigraph::Result::Ok(()) +/// # Result::<_,Box>::Ok(()) /// ``` #[must_use] pub struct TripleWriter { @@ -131,7 +131,7 @@ impl TripleWriter { /// writer.finish()?; /// ///assert_eq!(buffer.as_slice(), " .\n".as_bytes()); -/// # oxigraph::Result::Ok(()) +/// # Result::<_,Box>::Ok(()) /// ``` #[allow(missing_copy_implementations)] pub struct DatasetSerializer { @@ -174,7 +174,7 @@ impl DatasetSerializer { /// writer.finish()?; /// ///assert_eq!(buffer.as_slice(), " .\n".as_bytes()); -/// # oxigraph::Result::Ok(()) +/// # Result::<_,Box>::Ok(()) /// ``` #[must_use] pub struct QuadWriter { diff --git a/lib/src/lib.rs b/lib/src/lib.rs index c97664eb..b7d893b8 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -34,7 +34,7 @@ //! if let QueryResult::Solutions(mut solutions) = store.query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default())? { //! assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into())); //! } -//! # oxigraph::Result::Ok(()) +//! # Result::<_,Box>::Ok(()) //! ``` #![deny( future_incompatible, @@ -109,8 +109,10 @@ pub mod model; pub mod sparql; pub mod store; -pub use error::Error; -pub type Result = ::std::result::Result; +#[deprecated(note = "Use oxigraph::sparql::EvaluationError instead")] +pub type Error = crate::sparql::EvaluationError; +#[deprecated(note = "Use Result<_, oxigraph::sparql::EvaluationError> instead")] +pub type Result = ::std::result::Result; #[deprecated(note = "Use oxigraph::io::DatasetFormat instead")] pub type DatasetSyntax = crate::io::DatasetFormat; #[deprecated(note = "Use oxigraph::io::FileSyntax instead")] diff --git a/lib/src/model/blank_node.rs b/lib/src/model/blank_node.rs index ad9745a5..59081e9b 100644 --- a/lib/src/model/blank_node.rs +++ b/lib/src/model/blank_node.rs @@ -12,9 +12,16 @@ use std::str; /// It is also possible to create a blank node from a blank node identifier using the `BlankNode::new` method. /// The blank node identifier must be valid according to N-Triples, Turtle and SPARQL grammars. /// -/// The default string formatter is returning a N-Triples, Turtle and SPARQL compatible representation. -/// `BlankNode::default().to_string()` should return something like `_:00112233445566778899aabbccddeeff` +/// The default string formatter is returning a N-Triples, Turtle and SPARQL compatible representation: +/// ``` +/// use oxigraph::model::BlankNode; /// +/// assert_eq!( +/// "_:a122", +/// BlankNode::new("a122")?.to_string() +/// ); +/// # Result::<_,oxigraph::model::BlankNodeIdParseError>::Ok(()) +/// ``` #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct BlankNode(BlankNodeContent); diff --git a/lib/src/model/named_node.rs b/lib/src/model/named_node.rs index 02da9008..dd837f61 100644 --- a/lib/src/model/named_node.rs +++ b/lib/src/model/named_node.rs @@ -10,8 +10,9 @@ use std::fmt; /// /// assert_eq!( /// "", -/// NamedNode::new("http://example.com/foo").unwrap().to_string() -/// ) +/// NamedNode::new("http://example.com/foo")?.to_string() +/// ); +/// # Result::<_,oxigraph::model::IriParseError>::Ok(()) /// ``` #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] pub struct NamedNode { diff --git a/lib/src/sparql/error.rs b/lib/src/sparql/error.rs new file mode 100644 index 00000000..0e60d6ab --- /dev/null +++ b/lib/src/sparql/error.rs @@ -0,0 +1,109 @@ +use crate::error::Infallible; +use crate::sparql::ParseError; +use std::error; +use std::fmt; +use std::io; + +/// SPARQL evaluation error. +/// +/// The `wrap` method allows us to make this type wrap any implementation of `std::error::Error`. +/// This type also avoids heap allocations for the most common cases of evaluation errors. +#[derive(Debug)] +#[non_exhaustive] +pub enum EvaluationError { + /// An error in SPARQL query parsing + Parsing(ParseError), + /// An error returned during store IOs or during results write + Io(io::Error), + /// An error returned during the query evaluation itself + Query(QueryError), +} + +#[derive(Debug)] +pub struct QueryError { + inner: QueryErrorKind, +} + +#[derive(Debug)] +enum QueryErrorKind { + Msg { msg: String }, + Other(Box), +} + +impl fmt::Display for EvaluationError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Parsing(error) => error.fmt(f), + Self::Io(error) => error.fmt(f), + Self::Query(error) => error.fmt(f), + } + } +} + +impl fmt::Display for QueryError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.inner { + QueryErrorKind::Msg { msg } => write!(f, "{}", msg), + QueryErrorKind::Other(error) => error.fmt(f), + } + } +} + +impl error::Error for EvaluationError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Self::Parsing(e) => Some(e), + Self::Io(e) => Some(e), + Self::Query(e) => Some(e), + } + } +} + +impl error::Error for QueryError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match &self.inner { + QueryErrorKind::Msg { .. } => None, + QueryErrorKind::Other(e) => Some(e.as_ref()), + } + } +} + +impl EvaluationError { + /// Wraps another error. + pub(crate) fn wrap(error: impl error::Error + Send + Sync + 'static) -> Self { + Self::Query(QueryError { + inner: QueryErrorKind::Other(Box::new(error)), + }) + } + + /// Builds an error from a printable error message. + pub(crate) fn msg(msg: impl Into) -> Self { + Self::Query(QueryError { + inner: QueryErrorKind::Msg { msg: msg.into() }, + }) + } +} + +impl From for EvaluationError { + fn from(error: Infallible) -> Self { + match error {} + } +} + +impl From for EvaluationError { + fn from(error: std::convert::Infallible) -> Self { + match error {} + } +} + +impl From for EvaluationError { + fn from(error: ParseError) -> Self { + Self::Parsing(error) + } +} + +impl From for EvaluationError { + fn from(error: io::Error) -> Self { + Self::Io(error) + } +} diff --git a/lib/src/sparql/eval.rs b/lib/src/sparql/eval.rs index b2213937..3c4a14b7 100644 --- a/lib/src/sparql/eval.rs +++ b/lib/src/sparql/eval.rs @@ -2,13 +2,12 @@ use crate::model::xsd::*; use crate::model::BlankNode; use crate::model::Triple; use crate::sparql::algebra::{DatasetSpec, GraphPattern, QueryVariants}; +use crate::sparql::error::EvaluationError; use crate::sparql::model::*; use crate::sparql::plan::*; use crate::sparql::{Query, ServiceHandler}; use crate::store::numeric_encoder::*; use crate::store::ReadableEncodedStore; -use crate::Error; -use crate::Result; use digest::Digest; use md5::Md5; use oxilangtag::LanguageTag; @@ -29,13 +28,13 @@ use std::str; const REGEX_SIZE_LIMIT: usize = 1_000_000; -type EncodedTuplesIterator = Box>>; +type EncodedTuplesIterator = Box>>; pub(crate) struct SimpleEvaluator { dataset: Rc>, base_iri: Option>>, now: DateTime, - service_handler: Rc, + service_handler: Rc>, } impl Clone for SimpleEvaluator { @@ -53,7 +52,7 @@ impl SimpleEvaluator { pub fn new( dataset: Rc>, base_iri: Option>>, - service_handler: Rc, + service_handler: Rc>, ) -> Self { Self { dataset, @@ -67,14 +66,14 @@ impl SimpleEvaluator { &self, plan: &PlanNode, variables: Rc>, - ) -> Result { + ) -> Result { let iter = self.eval_plan(plan, EncodedTuple::with_capacity(variables.len())); Ok(QueryResult::Solutions( self.decode_bindings(iter, variables), )) } - pub fn evaluate_ask_plan(&self, plan: &PlanNode) -> Result { + pub fn evaluate_ask_plan(&self, plan: &PlanNode) -> Result { let from = EncodedTuple::with_capacity(plan.maybe_bound_variables().len()); match self.eval_plan(plan, from).next() { Some(Ok(_)) => Ok(QueryResult::Boolean(true)), @@ -87,7 +86,7 @@ impl SimpleEvaluator { &self, plan: &PlanNode, construct: Rc>, - ) -> Result { + ) -> Result { let from = EncodedTuple::with_capacity(plan.maybe_bound_variables().len()); Ok(QueryResult::Graph(QueryTriplesIterator { iter: Box::new(ConstructIterator { @@ -100,7 +99,7 @@ impl SimpleEvaluator { })) } - pub fn evaluate_describe_plan(&self, plan: &PlanNode) -> Result { + pub fn evaluate_describe_plan(&self, plan: &PlanNode) -> Result { let from = EncodedTuple::with_capacity(plan.maybe_bound_variables().len()); Ok(QueryResult::Graph(QueryTriplesIterator { iter: Box::new(DescribeIterator { @@ -228,7 +227,8 @@ impl SimpleEvaluator { if let Some(graph_name) = get_pattern_value(&graph_name, &tuple) { graph_name } else { - let result: EncodedTuplesIterator = Box::new(once(Err(Error::msg( + let result: EncodedTuplesIterator = + Box::new(once(Err(EvaluationError::msg( "Unknown graph name is not allowed when evaluating property path", )))); return result; @@ -502,11 +502,11 @@ impl SimpleEvaluator { graph_pattern: Rc, variables: Rc>, from: &EncodedTuple, - ) -> Result { + ) -> Result { if let QueryResult::Solutions(iter) = self.service_handler.handle( self.dataset.decode_named_node( get_pattern_value(service_name, from) - .ok_or_else(|| Error::msg("The SERVICE name is not bound"))?, + .ok_or_else(|| EvaluationError::msg("The SERVICE name is not bound"))?, )?, Query(QueryVariants::Select { dataset: Rc::new(DatasetSpec::default()), @@ -516,7 +516,7 @@ impl SimpleEvaluator { )? { Ok(self.encode_bindings(variables, iter)) } else { - Err(Error::msg( + Err(EvaluationError::msg( "The service call has not returned a set of solutions", )) } @@ -570,7 +570,7 @@ impl SimpleEvaluator { path: &PlanPropertyPath, start: EncodedTerm, graph_name: EncodedTerm, - ) -> Box>> { + ) -> Box>> { match path { PlanPropertyPath::PredicatePath(p) => Box::new( self.dataset @@ -633,7 +633,7 @@ impl SimpleEvaluator { path: &PlanPropertyPath, end: EncodedTerm, graph_name: EncodedTerm, - ) -> Box>> { + ) -> Box>> { match path { PlanPropertyPath::PredicatePath(p) => Box::new( self.dataset @@ -695,7 +695,7 @@ impl SimpleEvaluator { &self, path: &PlanPropertyPath, graph_name: EncodedTerm, - ) -> Box>> { + ) -> Box>> { match path { PlanPropertyPath::PredicatePath(p) => Box::new( self.dataset @@ -770,7 +770,7 @@ impl SimpleEvaluator { fn get_subject_or_object_identity_pairs( &self, graph_name: EncodedTerm, - ) -> impl Iterator> { + ) -> impl Iterator> { self.dataset .quads_for_pattern(None, None, None, Some(graph_name)) .flat_map_ok(|t| once(Ok(t.subject)).chain(once(Ok(t.object)))) @@ -2160,13 +2160,13 @@ pub fn are_compatible_and_not_disjointed(a: &EncodedTuple, b: &EncodedTuple) -> struct JoinIterator { left: Vec, right_iter: EncodedTuplesIterator, - buffered_results: Vec>, + buffered_results: Vec>, } impl Iterator for JoinIterator { - type Item = Result; + type Item = Result; - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option> { loop { if let Some(result) = self.buffered_results.pop() { return Some(result); @@ -2190,9 +2190,9 @@ struct AntiJoinIterator { } impl Iterator for AntiJoinIterator { - type Item = Result; + type Item = Result; - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option> { loop { match self.left_iter.next()? { Ok(left_tuple) => { @@ -2217,9 +2217,9 @@ struct LeftJoinIterator { } impl Iterator for LeftJoinIterator { - type Item = Result; + type Item = Result; - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option> { if let Some(tuple) = self.current_right.next() { return Some(tuple); } @@ -2247,9 +2247,9 @@ struct BadLeftJoinIterator { } impl Iterator for BadLeftJoinIterator { - type Item = Result; + type Item = Result; - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option> { while let Some(right_tuple) = self.current_right.next() { match right_tuple { Ok(right_tuple) => { @@ -2298,9 +2298,9 @@ struct UnionIterator { } impl Iterator for UnionIterator { - type Item = Result; + type Item = Result; - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option> { loop { if let Some(tuple) = self.current_iterator.next() { return Some(tuple); @@ -2320,14 +2320,14 @@ struct ConstructIterator { eval: SimpleEvaluator, iter: EncodedTuplesIterator, template: Rc>, - buffered_results: Vec>, + buffered_results: Vec>, bnodes: Vec, } impl Iterator for ConstructIterator { - type Item = Result; + type Item = Result; - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option> { loop { if let Some(result) = self.buffered_results.pop() { return Some(result); @@ -2379,7 +2379,7 @@ fn decode_triple( subject: EncodedTerm, predicate: EncodedTerm, object: EncodedTerm, -) -> Result { +) -> Result { Ok(Triple::new( decoder.decode_named_or_blank_node(subject)?, decoder.decode_named_node(predicate)?, @@ -2390,13 +2390,13 @@ fn decode_triple( struct DescribeIterator { eval: SimpleEvaluator, iter: EncodedTuplesIterator, - quads: Box>>, + quads: Box>>, } impl Iterator for DescribeIterator { - type Item = Result; + type Item = Result; - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option> { loop { if let Some(quad) = self.quads.next() { return Some(match quad { @@ -2449,10 +2449,10 @@ impl, I2: Iterator> Iterator } } -fn transitive_closure>>( - start: impl IntoIterator>, +fn transitive_closure>>( + start: impl IntoIterator>, next: impl Fn(T) -> NI, -) -> impl Iterator> { +) -> impl Iterator> { //TODO: optimize let mut all = HashSet::::default(); let mut errors = Vec::default(); @@ -2494,8 +2494,8 @@ fn transitive_closure>>( } fn hash_deduplicate( - iter: impl Iterator>, -) -> impl Iterator> { + iter: impl Iterator>, +) -> impl Iterator> { let mut already_seen = HashSet::with_capacity(iter.size_hint().0); iter.filter(move |e| { if let Ok(e) = e { @@ -2511,15 +2511,15 @@ fn hash_deduplicate( }) } -trait ResultIterator: Iterator> + Sized { - fn flat_map_ok U, U: IntoIterator>>( +trait ResultIterator: Iterator> + Sized { + fn flat_map_ok U, U: IntoIterator>>( self, f: F, ) -> FlatMapOk; } -impl> + Sized> ResultIterator for I { - fn flat_map_ok U, U: IntoIterator>>( +impl> + Sized> ResultIterator for I { + fn flat_map_ok U, U: IntoIterator>>( self, f: F, ) -> FlatMapOk { @@ -2534,21 +2534,26 @@ impl> + Sized> ResultIterator for I { struct FlatMapOk< T, O, - I: Iterator>, + I: Iterator>, F: FnMut(T) -> U, - U: IntoIterator>, + U: IntoIterator>, > { inner: I, f: F, current: Option, } -impl>, F: FnMut(T) -> U, U: IntoIterator>> - Iterator for FlatMapOk +impl< + T, + O, + I: Iterator>, + F: FnMut(T) -> U, + U: IntoIterator>, + > Iterator for FlatMapOk { - type Item = Result; + type Item = Result; - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option> { loop { if let Some(current) = &mut self.current { if let Some(next) = current.next() { diff --git a/lib/src/sparql/json_results.rs b/lib/src/sparql/json_results.rs index 231041d1..8761dd42 100644 --- a/lib/src/sparql/json_results.rs +++ b/lib/src/sparql/json_results.rs @@ -1,12 +1,15 @@ //! Implementation of [SPARQL Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/) +use crate::error::invalid_input_error; use crate::model::*; +use crate::sparql::error::EvaluationError; use crate::sparql::model::*; -use crate::Error; -use crate::Result; use std::io::Write; -pub fn write_json_results(results: QueryResult, mut sink: impl Write) -> Result<()> { +pub fn write_json_results( + results: QueryResult, + mut sink: impl Write, +) -> Result<(), EvaluationError> { match results { QueryResult::Boolean(value) => { sink.write_all(b"{\"head\":{},\"boolean\":")?; @@ -73,15 +76,16 @@ pub fn write_json_results(results: QueryResult, mut sink: impl Write) -> Result< sink.write_all(b"]}}")?; } QueryResult::Graph(_) => { - return Err(Error::msg( + return Err(invalid_input_error( "Graphs could not be formatted to SPARQL query results XML format", - )); + ) + .into()); } } Ok(()) } -fn write_escaped_json_string(s: &str, mut sink: impl Write) -> Result<()> { +fn write_escaped_json_string(s: &str, mut sink: impl Write) -> Result<(), EvaluationError> { sink.write_all(b"\"")?; for c in s.chars() { match c { diff --git a/lib/src/sparql/mod.rs b/lib/src/sparql/mod.rs index fb685eb7..2b75ae77 100644 --- a/lib/src/sparql/mod.rs +++ b/lib/src/sparql/mod.rs @@ -1,6 +1,7 @@ //! [SPARQL](https://www.w3.org/TR/sparql11-overview/) implementation. mod algebra; +mod error; mod eval; mod json_results; mod model; @@ -12,27 +13,26 @@ mod xml_results; use crate::model::NamedNode; use crate::sparql::algebra::QueryVariants; use crate::sparql::eval::SimpleEvaluator; +pub use crate::sparql::model::QuerySolution; +pub use crate::sparql::model::QuerySolutionsIterator; +pub use crate::sparql::model::QueryTriplesIterator; use crate::sparql::plan::TripleTemplate; use crate::sparql::plan::{DatasetView, PlanNode}; use crate::sparql::plan_builder::PlanBuilder; use crate::store::ReadableEncodedStore; -use crate::Error; -use crate::Result; - -pub use crate::sparql::model::QuerySolution; -pub use crate::sparql::model::QuerySolutionsIterator; -pub use crate::sparql::model::QueryTriplesIterator; +use std::convert::TryInto; +use std::rc::Rc; #[deprecated(note = "Please directly use QuerySolutionsIterator type instead")] pub type BindingsIterator<'a> = QuerySolutionsIterator; pub use crate::sparql::model::QueryResult; pub use crate::sparql::model::QueryResultFormat; #[deprecated(note = "Use QueryResultFormat instead")] pub type QueryResultSyntax = QueryResultFormat; +pub use crate::sparql::error::EvaluationError; pub use crate::sparql::model::Variable; +pub use crate::sparql::parser::ParseError; pub use crate::sparql::parser::Query; -pub use crate::sparql::parser::SparqlParseError; -use std::convert::TryInto; -use std::rc::Rc; +use std::error::Error; /// A prepared [SPARQL query](https://www.w3.org/TR/sparql11-query/) #[deprecated( @@ -70,9 +70,9 @@ enum SimplePreparedQueryAction { impl SimplePreparedQuery { pub(crate) fn new( store: S, - query: impl TryInto>, + query: impl TryInto>, options: QueryOptions, - ) -> Result { + ) -> Result { let dataset = Rc::new(DatasetView::new(store, options.default_graph_as_union)); Ok(Self(match query.try_into().map_err(|e| e.into())?.0 { QueryVariants::Select { @@ -124,7 +124,7 @@ impl SimplePreparedQuery { } /// Evaluates the query and returns its results - pub fn exec(&self) -> Result { + pub fn exec(&self) -> Result { match &self.0 { SimplePreparedQueryAction::Select { plan, @@ -144,14 +144,46 @@ impl SimplePreparedQuery { } } +/// Options for SPARQL query evaluation +#[derive(Clone)] +pub struct QueryOptions { + pub(crate) default_graph_as_union: bool, + pub(crate) service_handler: Rc>, +} + +impl Default for QueryOptions { + fn default() -> Self { + Self { + default_graph_as_union: false, + service_handler: Rc::new(EmptyServiceHandler), + } + } +} + +impl QueryOptions { + /// Consider the union of all graphs in the store as the default graph + pub const fn with_default_graph_as_union(mut self) -> Self { + self.default_graph_as_union = true; + self + } + + /// Use a given `ServiceHandler` to execute SPARQL SERVICE calls + pub fn with_service_handler(mut self, service_handler: impl ServiceHandler + 'static) -> Self { + self.service_handler = Rc::new(ErrorConversionServiceHandler { + handler: service_handler, + }); + self + } +} + /// Handler for SPARQL SERVICEs. /// /// Might be used to implement [SPARQL 1.1 Federated Query](https://www.w3.org/TR/sparql11-federated-query/) /// /// ``` -/// use oxigraph::{MemoryStore, Result}; +/// use oxigraph::MemoryStore; /// use oxigraph::model::*; -/// use oxigraph::sparql::{QueryOptions, QueryResult, ServiceHandler, Query}; +/// use oxigraph::sparql::{QueryOptions, QueryResult, ServiceHandler, Query, EvaluationError}; /// /// #[derive(Default)] /// struct TestServiceHandler { @@ -159,7 +191,9 @@ impl SimplePreparedQuery { /// } /// /// impl ServiceHandler for TestServiceHandler { -/// fn handle(&self,service_name: NamedNode, query: Query) -> Result { +/// type Error = EvaluationError; +/// +/// fn handle(&self,service_name: NamedNode, query: Query) -> Result { /// if service_name == "http://example.com/service" { /// self.store.query(query, QueryOptions::default()) /// } else { @@ -179,47 +213,41 @@ impl SimplePreparedQuery { /// )? { /// assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into())); /// } -/// # Result::Ok(()) +/// # Result::<_,Box>::Ok(()) /// ``` pub trait ServiceHandler { + 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; + fn handle(&self, service_name: NamedNode, query: Query) -> Result; } struct EmptyServiceHandler; impl ServiceHandler for EmptyServiceHandler { - fn handle(&self, _: NamedNode, _: Query) -> Result { - Err(Error::msg("The SERVICE feature is not implemented")) - } -} + type Error = EvaluationError; -/// Options for SPARQL query evaluation -#[derive(Clone)] -pub struct QueryOptions { - pub(crate) default_graph_as_union: bool, - pub(crate) service_handler: Rc, + fn handle(&self, _: NamedNode, _: Query) -> Result { + Err(EvaluationError::msg( + "The SERVICE feature is not implemented", + )) + } } -impl Default for QueryOptions { - fn default() -> Self { - Self { - default_graph_as_union: false, - service_handler: Rc::new(EmptyServiceHandler), - } - } +struct ErrorConversionServiceHandler { + handler: S, } -impl QueryOptions { - /// Consider the union of all graphs in the store as the default graph - pub const fn with_default_graph_as_union(mut self) -> Self { - self.default_graph_as_union = true; - self - } +impl ServiceHandler for ErrorConversionServiceHandler { + type Error = EvaluationError; - /// Use a given `ServiceHandler` to execute SPARQL SERVICE calls - pub fn with_service_handler(mut self, service_handler: impl ServiceHandler + 'static) -> Self { - self.service_handler = Rc::new(service_handler); - self + fn handle( + &self, + service_name: NamedNode, + query: Query, + ) -> Result { + self.handler + .handle(service_name, query) + .map_err(EvaluationError::wrap) } } diff --git a/lib/src/sparql/model.rs b/lib/src/sparql/model.rs index 6274dbd7..08a8afab 100644 --- a/lib/src/sparql/model.rs +++ b/lib/src/sparql/model.rs @@ -1,10 +1,11 @@ +use crate::error::invalid_input_error; use crate::io::GraphSerializer; #[allow(deprecated)] use crate::io::{FileSyntax, GraphFormat}; use crate::model::*; +use crate::sparql::error::EvaluationError; use crate::sparql::json_results::write_json_results; use crate::sparql::xml_results::{read_xml_results, write_xml_results}; -use crate::{Error, Result}; use rand::random; use std::fmt; use std::io::{BufRead, Write}; @@ -21,13 +22,16 @@ pub enum QueryResult { } impl QueryResult { - pub fn read(reader: impl BufRead + 'static, format: QueryResultFormat) -> Result { + pub fn read( + reader: impl BufRead + 'static, + format: QueryResultFormat, + ) -> Result { match format { QueryResultFormat::Xml => read_xml_results(reader), - QueryResultFormat::Json => Err(Error::msg( - //TODO: implement + QueryResultFormat::Json => Err(invalid_input_error( "JSON SPARQL results format parsing has not been implemented yet", - )), + ) + .into()), //TODO: implement } } @@ -47,9 +51,13 @@ impl QueryResult { /// let mut results = Vec::new(); /// store.query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default())?.write(&mut results, QueryResultFormat::Json)?; /// assert_eq!(results, "{\"head\":{\"vars\":[\"s\"]},\"results\":{\"bindings\":[{\"s\":{\"type\":\"uri\",\"value\":\"http://example.com\"}}]}}".as_bytes()); - /// # oxigraph::Result::Ok(()) + /// # Result::<_,Box>::Ok(()) /// ``` - pub fn write(self, writer: impl Write, format: QueryResultFormat) -> Result<()> { + pub fn write( + self, + writer: impl Write, + format: QueryResultFormat, + ) -> Result<(), EvaluationError> { match format { QueryResultFormat::Xml => write_xml_results(self, writer), QueryResultFormat::Json => write_json_results(self, writer), @@ -75,9 +83,13 @@ impl QueryResult { /// let mut results = Vec::new(); /// store.query("CONSTRUCT WHERE { ?s ?p ?o }", QueryOptions::default())?.write_graph(&mut results, GraphFormat::NTriples)?; /// assert_eq!(results, graph); - /// # oxigraph::Result::Ok(()) + /// # Result::<_,Box>::Ok(()) /// ``` - pub fn write_graph(self, write: impl Write, format: GraphFormat) -> Result<()> { + pub fn write_graph( + self, + write: impl Write, + format: GraphFormat, + ) -> Result<(), EvaluationError> { if let QueryResult::Graph(triples) = self { let mut writer = GraphSerializer::from_format(format).triple_writer(write)?; for triple in triples { @@ -86,9 +98,10 @@ impl QueryResult { writer.finish()?; Ok(()) } else { - Err(Error::msg( - "Bindings or booleans could not be formatted as an RDF graph", - )) + Err( + invalid_input_error("Bindings or booleans could not be formatted as an RDF graph") + .into(), + ) } } } @@ -212,17 +225,17 @@ impl FileSyntax for QueryResultFormat { /// println!("{:?}", solution?.get("s")); /// } /// } -/// # oxigraph::Result::Ok(()) +/// # Result::<_,Box>::Ok(()) /// ``` pub struct QuerySolutionsIterator { variables: Rc>, - iter: Box>>>>, + iter: Box>, EvaluationError>>>, } impl QuerySolutionsIterator { pub fn new( variables: Rc>, - iter: Box>>>>, + iter: Box>, EvaluationError>>>, ) -> Self { Self { variables, iter } } @@ -237,14 +250,16 @@ impl QuerySolutionsIterator { /// if let QueryResult::Solutions(solutions) = store.query("SELECT ?s ?o WHERE { ?s ?p ?o }", QueryOptions::default())? { /// assert_eq!(solutions.variables(), &[Variable::new("s"), Variable::new("o")]); /// } - /// # oxigraph::Result::Ok(()) + /// # Result::<_,Box>::Ok(()) /// ``` pub fn variables(&self) -> &[Variable] { &*self.variables } #[deprecated(note = "Please directly use QuerySolutionsIterator as an iterator instead")] - pub fn into_values_iter(self) -> Box>>>> { + pub fn into_values_iter( + self, + ) -> Box>, EvaluationError>>> { self.iter } @@ -253,16 +268,16 @@ impl QuerySolutionsIterator { self, ) -> ( Vec, - Box>>>>, + Box>, EvaluationError>>>, ) { ((*self.variables).clone(), self.iter) } } impl Iterator for QuerySolutionsIterator { - type Item = Result; + type Item = Result; - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option> { Some(self.iter.next()?.map(|values| QuerySolution { values, variables: self.variables.clone(), @@ -359,16 +374,16 @@ impl VariableSolutionIndex for Variable { /// println!("{}", triple?); /// } /// } -/// # oxigraph::Result::Ok(()) +/// # Result::<_,Box>::Ok(()) /// ``` pub struct QueryTriplesIterator { - pub(crate) iter: Box>>, + pub(crate) iter: Box>>, } impl Iterator for QueryTriplesIterator { - type Item = Result; + type Item = Result; - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option> { self.iter.next() } @@ -409,7 +424,7 @@ impl Variable { } #[deprecated(note = "Please use as_str instead")] - pub fn name(&self) -> Result<&str> { + pub fn name(&self) -> Result<&str, EvaluationError> { Ok(self.as_str()) } diff --git a/lib/src/sparql/parser.rs b/lib/src/sparql/parser.rs index cd85c42c..3ebbf25c 100644 --- a/lib/src/sparql/parser.rs +++ b/lib/src/sparql/parser.rs @@ -4,7 +4,6 @@ use crate::model::*; use crate::sparql::algebra::*; use crate::sparql::model::*; use oxiri::{Iri, IriParseError}; -use peg::error::ParseError; use peg::parser; use peg::str::LineCol; use std::borrow::Cow; @@ -25,7 +24,7 @@ use std::{char, fmt}; /// let query = Query::parse(query_str, None)?; /// /// assert_eq!(query.to_string(), query_str); -/// # oxigraph::Result::Ok(()) +/// # Result::Ok::<_, oxigraph::sparql::ParseError>(()) /// ``` #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct Query(pub(crate) QueryVariants); @@ -38,12 +37,12 @@ impl fmt::Display for Query { 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 { + pub fn parse(query: &str, base_iri: Option<&str>) -> Result { let mut state = ParserState { base_iri: if let Some(base_iri) = base_iri { Some(Rc::new(Iri::parse(base_iri.to_owned()).map_err(|e| { - SparqlParseError { - inner: SparqlParseErrorKind::InvalidBaseIri(e), + ParseError { + inner: ParseErrorKind::InvalidBaseIri(e), } })?)) } else { @@ -57,8 +56,8 @@ impl Query { Ok(Self( parser::QueryUnit(&unescape_unicode_codepoints(query), &mut state).map_err(|e| { - SparqlParseError { - inner: SparqlParseErrorKind::Parser(e), + ParseError { + inner: ParseErrorKind::Parser(e), } })?, )) @@ -66,53 +65,53 @@ impl Query { } impl FromStr for Query { - type Err = SparqlParseError; + type Err = ParseError; - fn from_str(query: &str) -> Result { + fn from_str(query: &str) -> Result { Self::parse(query, None) } } impl<'a> TryFrom<&'a str> for Query { - type Error = SparqlParseError; + type Error = ParseError; - fn try_from(query: &str) -> Result { + fn try_from(query: &str) -> Result { Self::from_str(query) } } impl<'a> TryFrom<&'a String> for Query { - type Error = SparqlParseError; + type Error = ParseError; - fn try_from(query: &String) -> Result { + fn try_from(query: &String) -> Result { Self::from_str(query) } } /// Error returned during SPARQL parsing. #[derive(Debug)] -pub struct SparqlParseError { - inner: SparqlParseErrorKind, +pub struct ParseError { + inner: ParseErrorKind, } #[derive(Debug)] -enum SparqlParseErrorKind { +enum ParseErrorKind { InvalidBaseIri(IriParseError), - Parser(ParseError), + Parser(peg::error::ParseError), } -impl fmt::Display for SparqlParseError { +impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.inner { - SparqlParseErrorKind::InvalidBaseIri(e) => { + ParseErrorKind::InvalidBaseIri(e) => { write!(f, "Invalid SPARQL base IRI provided: {}", e) } - SparqlParseErrorKind::Parser(e) => e.fmt(f), + ParseErrorKind::Parser(e) => e.fmt(f), } } } -impl Error for SparqlParseError {} +impl Error for ParseError {} struct FocusedTriplePattern { focus: F, diff --git a/lib/src/sparql/plan.rs b/lib/src/sparql/plan.rs index 261f1b8f..94f25393 100644 --- a/lib/src/sparql/plan.rs +++ b/lib/src/sparql/plan.rs @@ -1,12 +1,12 @@ use crate::error::UnwrapInfallible; use crate::sparql::algebra::GraphPattern; +use crate::sparql::error::EvaluationError; use crate::sparql::model::Variable; use crate::store::numeric_encoder::{ EncodedQuad, EncodedTerm, Encoder, MemoryStrStore, StrContainer, StrHash, StrLookup, ENCODED_DEFAULT_GRAPH, }; use crate::store::ReadableEncodedStore; -use crate::Result; use std::cell::{RefCell, RefMut}; use std::collections::BTreeSet; use std::io; @@ -581,7 +581,7 @@ impl DatasetView { predicate: Option, object: Option, graph_name: Option, - ) -> Box>> { + ) -> Box>> { if graph_name == None { Box::new( map_io_err( @@ -625,15 +625,15 @@ impl DatasetView { } fn map_io_err<'a, T>( - iter: impl Iterator>> + 'a, -) -> impl Iterator> + 'a { + iter: impl Iterator>> + 'a, +) -> impl Iterator> + 'a { iter.map(|e| e.map_err(|e| e.into().into())) } impl StrLookup for DatasetView { type Error = ::Error; - fn get_str(&self, id: StrHash) -> std::result::Result, Self::Error> { + fn get_str(&self, id: StrHash) -> Result, Self::Error> { if let Some(value) = self.extra.borrow().get_str(id).unwrap_infallible() { Ok(Some(value)) } else { @@ -650,7 +650,7 @@ struct DatasetViewStrContainer<'a, S: ReadableEncodedStore> { impl<'a, S: ReadableEncodedStore> StrContainer for DatasetViewStrContainer<'a, S> { type Error = ::Error; - fn insert_str(&mut self, key: StrHash, value: &str) -> std::result::Result<(), Self::Error> { + fn insert_str(&mut self, key: StrHash, value: &str) -> Result<(), Self::Error> { if self.store.get_str(key)?.is_none() { self.extra.insert_str(key, value).unwrap(); Ok(()) diff --git a/lib/src/sparql/plan_builder.rs b/lib/src/sparql/plan_builder.rs index 65f9691b..5d69f065 100644 --- a/lib/src/sparql/plan_builder.rs +++ b/lib/src/sparql/plan_builder.rs @@ -1,10 +1,9 @@ use crate::model::{BlankNode, Term}; use crate::sparql::algebra::*; +use crate::sparql::error::EvaluationError; use crate::sparql::model::*; use crate::sparql::plan::*; use crate::store::numeric_encoder::{Encoder, ENCODED_DEFAULT_GRAPH}; -use crate::Error; -use crate::Result; use std::collections::{BTreeSet, HashSet}; use std::rc::Rc; @@ -13,7 +12,10 @@ pub(crate) struct PlanBuilder { } impl PlanBuilder { - pub fn build(encoder: E, pattern: &GraphPattern) -> Result<(PlanNode, Vec)> { + pub fn build( + encoder: E, + pattern: &GraphPattern, + ) -> Result<(PlanNode, Vec), EvaluationError> { let mut variables = Vec::default(); let plan = PlanBuilder { encoder }.build_for_graph_pattern( pattern, @@ -27,7 +29,7 @@ impl PlanBuilder { encoder: E, template: &[TriplePattern], mut variables: Vec, - ) -> Result> { + ) -> Result, EvaluationError> { PlanBuilder { encoder }.build_for_graph_template(template, &mut variables) } @@ -36,7 +38,7 @@ impl PlanBuilder { pattern: &GraphPattern, variables: &mut Vec, graph_name: PatternValue, - ) -> Result { + ) -> Result { Ok(match pattern { GraphPattern::BGP(p) => self.build_for_bgp(p, variables, graph_name)?, GraphPattern::Join(a, b) => PlanNode::Join { @@ -143,7 +145,7 @@ impl PlanBuilder { variable_key(variables, v), )) }) - .collect::>>()?, + .collect::, EvaluationError>>()?, ), } } @@ -151,7 +153,7 @@ impl PlanBuilder { tuples: self.encode_bindings(bs, variables)?, }, GraphPattern::OrderBy(l, o) => { - let by: Result> = o + let by: Result, EvaluationError> = o .iter() .map(|comp| match comp { OrderComparator::Asc(e) => Ok(Comparator::Asc( @@ -216,7 +218,7 @@ impl PlanBuilder { p: &[TripleOrPathPattern], variables: &mut Vec, graph_name: PatternValue, - ) -> Result { + ) -> Result { let mut plan = PlanNode::Init; for pattern in sort_bgp(p) { plan = match pattern { @@ -242,7 +244,7 @@ impl PlanBuilder { Ok(plan) } - fn build_for_path(&mut self, path: &PropertyPath) -> Result { + fn build_for_path(&mut self, path: &PropertyPath) -> Result { Ok(match path { PropertyPath::PredicatePath(p) => PlanPropertyPath::PredicatePath( self.encoder.encode_named_node(p).map_err(|e| e.into())?, @@ -270,7 +272,7 @@ impl PlanBuilder { PropertyPath::NegatedPropertySet(p) => PlanPropertyPath::NegatedPropertySet(Rc::new( p.iter() .map(|p| self.encoder.encode_named_node(p).map_err(|e| e.into())) - .collect::, _>>()?, + .collect::, _>>()?, )), }) } @@ -280,7 +282,7 @@ impl PlanBuilder { expression: &Expression, variables: &mut Vec, graph_name: PatternValue, - ) -> Result { + ) -> Result { Ok(match expression { Expression::NamedNode(node) => PlanExpression::Constant( self.encoder.encode_named_node(node).map_err(|e| e.into())?, @@ -676,7 +678,7 @@ impl PlanBuilder { "string", )? } else { - return Err(Error::msg(format!( + return Err(EvaluationError::msg(format!( "Not supported custom function {}", expression ))); @@ -697,7 +699,7 @@ impl PlanBuilder { variables: &mut Vec, graph_name: PatternValue, name: &'static str, - ) -> Result { + ) -> Result { if parameters.len() == 1 { Ok(constructor(Box::new(self.build_for_expression( ¶meters[0], @@ -705,7 +707,7 @@ impl PlanBuilder { graph_name, )?))) } else { - Err(Error::msg(format!( + Err(EvaluationError::msg(format!( "The xsd:{} casting takes only one parameter", name ))) @@ -717,7 +719,7 @@ impl PlanBuilder { l: &[Expression], variables: &mut Vec, graph_name: PatternValue, - ) -> Result> { + ) -> Result, EvaluationError> { l.iter() .map(|e| self.build_for_expression(e, variables, graph_name)) .collect() @@ -727,7 +729,7 @@ impl PlanBuilder { &mut self, term_or_variable: &TermOrVariable, variables: &mut Vec, - ) -> Result { + ) -> Result { Ok(match term_or_variable { TermOrVariable::Variable(variable) => { PatternValue::Variable(variable_key(variables, variable)) @@ -746,7 +748,7 @@ impl PlanBuilder { &mut self, named_node_or_variable: &NamedNodeOrVariable, variables: &mut Vec, - ) -> Result { + ) -> Result { Ok(match named_node_or_variable { NamedNodeOrVariable::NamedNode(named_node) => PatternValue::Constant( self.encoder @@ -763,7 +765,7 @@ impl PlanBuilder { &mut self, bindings: &StaticBindings, variables: &mut Vec, - ) -> Result> { + ) -> Result, EvaluationError> { let bindings_variables_keys = bindings .variables() .iter() @@ -791,7 +793,7 @@ impl PlanBuilder { aggregate: &Aggregation, variables: &mut Vec, graph_name: PatternValue, - ) -> Result { + ) -> Result { Ok(match aggregate { Aggregation::Count(e, distinct) => PlanAggregation { function: PlanAggregationFunction::Count, @@ -840,7 +842,7 @@ impl PlanBuilder { &mut self, template: &[TriplePattern], variables: &mut Vec, - ) -> Result> { + ) -> Result, EvaluationError> { let mut bnodes = Vec::default(); template .iter() @@ -868,7 +870,7 @@ impl PlanBuilder { term_or_variable: &TermOrVariable, variables: &mut Vec, bnodes: &mut Vec, - ) -> Result { + ) -> Result { Ok(match term_or_variable { TermOrVariable::Variable(variable) => { TripleTemplateValue::Variable(variable_key(variables, variable)) @@ -886,7 +888,7 @@ impl PlanBuilder { &mut self, named_node_or_variable: &NamedNodeOrVariable, variables: &mut Vec, - ) -> Result { + ) -> Result { Ok(match named_node_or_variable { NamedNodeOrVariable::Variable(variable) => { TripleTemplateValue::Variable(variable_key(variables, variable)) diff --git a/lib/src/sparql/xml_results.rs b/lib/src/sparql/xml_results.rs index aee096f7..c472f3bc 100644 --- a/lib/src/sparql/xml_results.rs +++ b/lib/src/sparql/xml_results.rs @@ -1,9 +1,9 @@ //! Implementation of [SPARQL Query Results XML Format](http://www.w3.org/TR/rdf-sparql-XMLres/) +use crate::error::{invalid_data_error, invalid_input_error}; use crate::model::*; +use crate::sparql::error::EvaluationError; use crate::sparql::model::*; -use crate::Error; -use crate::Result; use quick_xml::events::BytesDecl; use quick_xml::events::BytesEnd; use quick_xml::events::BytesStart; @@ -17,93 +17,138 @@ use std::io::Write; use std::iter::empty; use std::rc::Rc; -pub fn write_xml_results(results: QueryResult, sink: impl Write) -> Result<()> { - let mut writer = Writer::new(sink); +pub fn write_xml_results(results: QueryResult, sink: impl Write) -> Result<(), EvaluationError> { match results { QueryResult::Boolean(value) => { - writer.write_event(Event::Decl(BytesDecl::new(b"1.0", None, None)))?; - let mut sparql_open = BytesStart::borrowed_name(b"sparql"); - sparql_open.push_attribute(("xmlns", "http://www.w3.org/2005/sparql-results#")); - writer.write_event(Event::Start(sparql_open))?; - writer.write_event(Event::Start(BytesStart::borrowed_name(b"head")))?; - writer.write_event(Event::End(BytesEnd::borrowed(b"head")))?; - writer.write_event(Event::Start(BytesStart::borrowed_name(b"boolean")))?; - writer.write_event(Event::Text(BytesText::from_plain_str(if value { - "true" - } else { - "false" - })))?; - writer.write_event(Event::End(BytesEnd::borrowed(b"boolean")))?; - writer.write_event(Event::End(BytesEnd::borrowed(b"sparql")))?; + write_boolean(value, sink).map_err(map_xml_error)?; + Ok(()) } - QueryResult::Solutions(solutions) => { - writer.write_event(Event::Decl(BytesDecl::new(b"1.0", None, None)))?; - let mut sparql_open = BytesStart::borrowed_name(b"sparql"); - sparql_open.push_attribute(("xmlns", "http://www.w3.org/2005/sparql-results#")); - writer.write_event(Event::Start(sparql_open))?; - writer.write_event(Event::Start(BytesStart::borrowed_name(b"head")))?; - for variable in solutions.variables() { - let mut variable_tag = BytesStart::borrowed_name(b"variable"); - variable_tag.push_attribute(("name", variable.as_str())); - writer.write_event(Event::Empty(variable_tag))?; - } - writer.write_event(Event::End(BytesEnd::borrowed(b"head")))?; - writer.write_event(Event::Start(BytesStart::borrowed_name(b"results")))?; - for solution in solutions { - let solution = solution?; - writer.write_event(Event::Start(BytesStart::borrowed_name(b"result")))?; - for (variable, value) in solution.iter() { - let mut binding_tag = BytesStart::borrowed_name(b"binding"); - binding_tag.push_attribute(("name", variable.as_str())); - writer.write_event(Event::Start(binding_tag))?; - match value { - Term::NamedNode(uri) => { - writer.write_event(Event::Start(BytesStart::borrowed_name(b"uri")))?; - writer.write_event(Event::Text(BytesText::from_plain_str( - uri.as_str(), - )))?; - writer.write_event(Event::End(BytesEnd::borrowed(b"uri")))?; - } - Term::BlankNode(bnode) => { - writer - .write_event(Event::Start(BytesStart::borrowed_name(b"bnode")))?; - writer.write_event(Event::Text(BytesText::from_plain_str( - bnode.as_str(), - )))?; - writer.write_event(Event::End(BytesEnd::borrowed(b"bnode")))?; - } - Term::Literal(literal) => { - let mut literal_tag = BytesStart::borrowed_name(b"literal"); - if let Some(language) = literal.language() { - literal_tag.push_attribute(("xml:lang", language)); - } else if !literal.is_plain() { - literal_tag - .push_attribute(("datatype", literal.datatype().as_str())); - } - writer.write_event(Event::Start(literal_tag))?; - writer.write_event(Event::Text(BytesText::from_plain_str( - literal.value(), - )))?; - writer.write_event(Event::End(BytesEnd::borrowed(b"literal")))?; - } + QueryResult::Solutions(solutions) => write_solutions(solutions, sink), + QueryResult::Graph(_) => Err(invalid_input_error( + "Graphs could not be formatted to SPARQL query results XML format", + ) + .into()), + } +} + +fn write_boolean(value: bool, sink: impl Write) -> Result<(), quick_xml::Error> { + let mut writer = Writer::new(sink); + writer.write_event(Event::Decl(BytesDecl::new(b"1.0", None, None)))?; + let mut sparql_open = BytesStart::borrowed_name(b"sparql"); + sparql_open.push_attribute(("xmlns", "http://www.w3.org/2005/sparql-results#")); + writer.write_event(Event::Start(sparql_open))?; + writer.write_event(Event::Start(BytesStart::borrowed_name(b"head")))?; + writer.write_event(Event::End(BytesEnd::borrowed(b"head")))?; + writer.write_event(Event::Start(BytesStart::borrowed_name(b"boolean")))?; + writer.write_event(Event::Text(BytesText::from_plain_str(if value { + "true" + } else { + "false" + })))?; + writer.write_event(Event::End(BytesEnd::borrowed(b"boolean")))?; + writer.write_event(Event::End(BytesEnd::borrowed(b"sparql")))?; + Ok(()) +} + +fn write_solutions( + solutions: QuerySolutionsIterator, + sink: impl Write, +) -> Result<(), EvaluationError> { + let mut writer = Writer::new(sink); + writer + .write_event(Event::Decl(BytesDecl::new(b"1.0", None, None))) + .map_err(map_xml_error)?; + let mut sparql_open = BytesStart::borrowed_name(b"sparql"); + sparql_open.push_attribute(("xmlns", "http://www.w3.org/2005/sparql-results#")); + writer + .write_event(Event::Start(sparql_open)) + .map_err(map_xml_error)?; + writer + .write_event(Event::Start(BytesStart::borrowed_name(b"head"))) + .map_err(map_xml_error)?; + for variable in solutions.variables() { + let mut variable_tag = BytesStart::borrowed_name(b"variable"); + variable_tag.push_attribute(("name", variable.as_str())); + writer + .write_event(Event::Empty(variable_tag)) + .map_err(map_xml_error)?; + } + writer + .write_event(Event::End(BytesEnd::borrowed(b"head"))) + .map_err(map_xml_error)?; + writer + .write_event(Event::Start(BytesStart::borrowed_name(b"results"))) + .map_err(map_xml_error)?; + for solution in solutions { + let solution = solution?; + writer + .write_event(Event::Start(BytesStart::borrowed_name(b"result"))) + .map_err(map_xml_error)?; + for (variable, value) in solution.iter() { + let mut binding_tag = BytesStart::borrowed_name(b"binding"); + binding_tag.push_attribute(("name", variable.as_str())); + writer + .write_event(Event::Start(binding_tag)) + .map_err(map_xml_error)?; + match value { + Term::NamedNode(uri) => { + writer + .write_event(Event::Start(BytesStart::borrowed_name(b"uri"))) + .map_err(map_xml_error)?; + writer + .write_event(Event::Text(BytesText::from_plain_str(uri.as_str()))) + .map_err(map_xml_error)?; + writer + .write_event(Event::End(BytesEnd::borrowed(b"uri"))) + .map_err(map_xml_error)?; + } + Term::BlankNode(bnode) => { + writer + .write_event(Event::Start(BytesStart::borrowed_name(b"bnode"))) + .map_err(map_xml_error)?; + writer + .write_event(Event::Text(BytesText::from_plain_str(bnode.as_str()))) + .map_err(map_xml_error)?; + writer + .write_event(Event::End(BytesEnd::borrowed(b"bnode"))) + .map_err(map_xml_error)?; + } + Term::Literal(literal) => { + let mut literal_tag = BytesStart::borrowed_name(b"literal"); + if let Some(language) = literal.language() { + literal_tag.push_attribute(("xml:lang", language)); + } else if !literal.is_plain() { + literal_tag.push_attribute(("datatype", literal.datatype().as_str())); } - writer.write_event(Event::End(BytesEnd::borrowed(b"binding")))?; + writer + .write_event(Event::Start(literal_tag)) + .map_err(map_xml_error)?; + writer + .write_event(Event::Text(BytesText::from_plain_str(literal.value()))) + .map_err(map_xml_error)?; + writer + .write_event(Event::End(BytesEnd::borrowed(b"literal"))) + .map_err(map_xml_error)?; } - writer.write_event(Event::End(BytesEnd::borrowed(b"result")))?; } - writer.write_event(Event::End(BytesEnd::borrowed(b"results")))?; - writer.write_event(Event::End(BytesEnd::borrowed(b"sparql")))?; - } - QueryResult::Graph(_) => { - return Err(Error::msg( - "Graphs could not be formatted to SPARQL query results XML format", - )); + writer + .write_event(Event::End(BytesEnd::borrowed(b"binding"))) + .map_err(map_xml_error)?; } + writer + .write_event(Event::End(BytesEnd::borrowed(b"result"))) + .map_err(map_xml_error)?; } + writer + .write_event(Event::End(BytesEnd::borrowed(b"results"))) + .map_err(map_xml_error)?; + writer + .write_event(Event::End(BytesEnd::borrowed(b"sparql"))) + .map_err(map_xml_error)?; Ok(()) } -pub fn read_xml_results(source: impl BufRead + 'static) -> Result { +pub fn read_xml_results(source: impl BufRead + 'static) -> Result { enum State { Start, Sparql, @@ -123,13 +168,16 @@ pub fn read_xml_results(source: impl BufRead + 'static) -> Result { //Read header loop { let event = { - let (ns, event) = reader.read_namespaced_event(&mut buffer, &mut namespace_buffer)?; + let (ns, event) = reader + .read_namespaced_event(&mut buffer, &mut namespace_buffer) + .map_err(map_xml_error)?; if let Some(ns) = ns { if ns != b"http://www.w3.org/2005/sparql-results#".as_ref() { - return Err(Error::msg(format!( + return Err(invalid_data_error(format!( "Unexpected namespace found in RDF/XML query result: {}", - reader.decode(ns)? - ))); + reader.decode(ns).map_err(map_xml_error)? + )) + .into()); } } event @@ -140,14 +188,14 @@ pub fn read_xml_results(source: impl BufRead + 'static) -> Result { if event.name() == b"sparql" { state = State::Sparql; } else { - return Err(Error::msg(format!("Expecting tag, found {}", reader.decode(event.name())?))); + return Err(invalid_data_error(format!("Expecting tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?)).into()); } } State::Sparql => { if event.name() == b"head" { state = State::Head; } else { - return Err(Error::msg(format!("Expecting tag, found {}", reader.decode(event.name())?))); + return Err(invalid_data_error(format!("Expecting tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?)).into()); } } State::Head => { @@ -155,12 +203,12 @@ pub fn read_xml_results(source: impl BufRead + 'static) -> Result { let name = event.attributes() .filter_map(|attr| attr.ok()) .find(|attr| attr.key == b"name") - .ok_or_else(|| Error::msg("No name attribute found for the tag"))?; - variables.push(name.unescape_and_decode_value(&reader)?); + .ok_or_else(|| invalid_data_error("No name attribute found for the tag"))?; + variables.push(name.unescape_and_decode_value(&reader).map_err(map_xml_error)?); } else if event.name() == b"link" { // no op } else { - return Err(Error::msg(format!("Expecting or tag, found {}", reader.decode(event.name())?))); + return Err(invalid_data_error(format!("Expecting or tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?)).into()); } } State::AfterHead => { @@ -181,17 +229,17 @@ pub fn read_xml_results(source: impl BufRead + 'static) -> Result { }), ))); } else if event.name() != b"link" && event.name() != b"results" && event.name() != b"boolean" { - return Err(Error::msg(format!("Expecting sparql tag, found {}", reader.decode(event.name())?))); + return Err(invalid_data_error(format!("Expecting sparql tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?)).into()); } } - State::Boolean => return Err(Error::msg(format!("Unexpected tag inside of tag: {}", reader.decode(event.name())?))) + State::Boolean => return Err(invalid_data_error(format!("Unexpected tag inside of tag: {}", reader.decode(event.name()).map_err(map_xml_error)?)).into()) }, Event::Empty(event) => match state { State::Sparql => { if event.name() == b"head" { state = State::AfterHead; } else { - return Err(Error::msg(format!("Expecting tag, found {}", reader.decode(event.name())?))); + return Err(invalid_data_error(format!("Expecting tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?)).into()); } } State::Head => { @@ -199,12 +247,12 @@ pub fn read_xml_results(source: impl BufRead + 'static) -> Result { let name = event.attributes() .filter_map(|v| v.ok()) .find(|attr| attr.key == b"name") - .ok_or_else(|| Error::msg("No name attribute found for the tag"))?; - variables.push(name.unescape_and_decode_value(&reader)?); + .ok_or_else(|| invalid_data_error("No name attribute found for the tag"))?; + variables.push(name.unescape_and_decode_value(&reader).map_err(map_xml_error)?); } else if event.name() == b"link" { // no op } else { - return Err(Error::msg(format!("Expecting or tag, found {}", reader.decode(event.name())?))); + return Err(invalid_data_error(format!("Expecting or tag, found {}", reader.decode(event.name()).map_err(map_xml_error)?)).into()); } }, State::AfterHead => { @@ -214,13 +262,13 @@ pub fn read_xml_results(source: impl BufRead + 'static) -> Result { Box::new(empty()), ))) } else { - return Err(Error::msg(format!("Unexpected autoclosing tag <{}>", reader.decode(event.name())?))) + return Err(invalid_data_error(format!("Unexpected autoclosing tag <{}>", reader.decode(event.name()).map_err(map_xml_error)?)).into()) } } - _ => return Err(Error::msg(format!("Unexpected autoclosing tag <{}>", reader.decode(event.name())?))) + _ => return Err(invalid_data_error(format!("Unexpected autoclosing tag <{}>", reader.decode(event.name()).map_err(map_xml_error)?)).into()) }, Event::Text(event) => { - let value = event.unescaped()?; + let value = event.unescaped().map_err(map_xml_error)?; return match state { State::Boolean => { return if value.as_ref() == b"true" { @@ -228,18 +276,18 @@ pub fn read_xml_results(source: impl BufRead + 'static) -> Result { } else if value.as_ref() == b"false" { Ok(QueryResult::Boolean(false)) } else { - Err(Error::msg(format!("Unexpected boolean value. Found {}", reader.decode(&value)?))) + Err(invalid_data_error(format!("Unexpected boolean value. Found {}", reader.decode(&value).map_err(map_xml_error)?)).into()) }; } - _ => Err(Error::msg(format!("Unexpected textual value found: {}", reader.decode(&value)?))) + _ => Err(invalid_data_error(format!("Unexpected textual value found: {}", reader.decode(&value).map_err(map_xml_error)?)).into()) }; }, Event::End(_) => if let State::Head = state { state = State::AfterHead; } else { - return Err(Error::msg("Unexpected early file end. All results file should have a and a or tag")); + return Err(invalid_data_error("Unexpected early file end. All results file should have a and a or tag").into()); }, - Event::Eof => return Err(Error::msg("Unexpected early file end. All results file should have a and a or tag")), + Event::Eof => return Err(invalid_data_error("Unexpected early file end. All results file should have a and a or tag").into()), _ => (), } } @@ -253,15 +301,15 @@ struct ResultsIterator { } impl Iterator for ResultsIterator { - type Item = Result>>; + type Item = Result>, EvaluationError>; - fn next(&mut self) -> Option>>> { + fn next(&mut self) -> Option>, EvaluationError>> { self.read_next().transpose() } } impl ResultsIterator { - fn read_next(&mut self) -> Result>>> { + fn read_next(&mut self) -> Result>>, EvaluationError> { enum State { Start, Result, @@ -283,13 +331,15 @@ impl ResultsIterator { loop { let (ns, event) = self .reader - .read_namespaced_event(&mut self.buffer, &mut self.namespace_buffer)?; + .read_namespaced_event(&mut self.buffer, &mut self.namespace_buffer) + .map_err(map_xml_error)?; if let Some(ns) = ns { if ns != b"http://www.w3.org/2005/sparql-results#".as_ref() { - return Err(Error::msg(format!( + return Err(invalid_data_error(format!( "Unexpected namespace found in RDF/XML query result: {}", - self.reader.decode(ns)? - ))); + self.reader.decode(ns).map_err(map_xml_error)? + )) + .into()); } } match event { @@ -298,10 +348,11 @@ impl ResultsIterator { if event.name() == b"result" { state = State::Result; } else { - return Err(Error::msg(format!( + return Err(invalid_data_error(format!( "Expecting , found {}", - self.reader.decode(event.name())? - ))); + self.reader.decode(event.name()).map_err(map_xml_error)? + )) + .into()); } } State::Result => { @@ -311,26 +362,33 @@ impl ResultsIterator { .filter_map(|v| v.ok()) .find(|attr| attr.key == b"name") { - Some(attr) => current_var = Some(attr.unescaped_value()?.to_vec()), + Some(attr) => { + current_var = Some( + attr.unescaped_value().map_err(map_xml_error)?.to_vec(), + ) + } None => { - return Err(Error::msg( + return Err(invalid_data_error( "No name attribute found for the tag", - )); + ) + .into()); } } state = State::Binding; } else { - return Err(Error::msg(format!( + return Err(invalid_data_error(format!( "Expecting , found {}", - self.reader.decode(event.name())? - ))); + self.reader.decode(event.name()).map_err(map_xml_error)? + )) + .into()); } } State::Binding => { if term.is_some() { - return Err(Error::msg( + return Err(invalid_data_error( "There is already a value for the current binding", - )); + ) + .into()); } if event.name() == b"uri" { state = State::Uri; @@ -340,37 +398,67 @@ impl ResultsIterator { for attr in event.attributes() { if let Ok(attr) = attr { if attr.key == b"xml:lang" { - lang = Some(attr.unescape_and_decode_value(&self.reader)?); + lang = Some( + attr.unescape_and_decode_value(&self.reader) + .map_err(map_xml_error)?, + ); } else if attr.key == b"datatype" { - datatype = Some(NamedNode::new( - attr.unescape_and_decode_value(&self.reader)?, - )?); + let iri = attr + .unescape_and_decode_value(&self.reader) + .map_err(map_xml_error)?; + datatype = Some(NamedNode::new(&iri).map_err(|e| { + invalid_data_error(format!( + "Invalid datatype IRI '{}': {}", + iri, e + )) + })?); } } } state = State::Literal; } else { - return Err(Error::msg(format!( + return Err(invalid_data_error(format!( "Expecting , or found {}", - self.reader.decode(event.name())? - ))); + self.reader.decode(event.name()).map_err(map_xml_error)? + )) + .into()); } } _ => (), }, Event::Text(event) => { - let data = event.unescaped()?; + let data = event.unescaped().map_err(map_xml_error)?; match state { State::Uri => { - term = Some(NamedNode::new(self.reader.decode(&data)?)?.into()) + let iri = self.reader.decode(&data).map_err(map_xml_error)?; + term = Some( + NamedNode::new(iri) + .map_err(|e| { + invalid_data_error(format!( + "Invalid IRI value '{}': {}", + iri, e + )) + })? + .into(), + ) } State::BNode => { - term = Some(BlankNode::new(self.reader.decode(&data)?)?.into()) + let bnode = self.reader.decode(&data).map_err(map_xml_error)?; + term = Some( + BlankNode::new(bnode) + .map_err(|e| { + invalid_data_error(format!( + "Invalid blank node value '{}': {}", + bnode, e + )) + })? + .into(), + ) } State::Literal => { term = Some( build_literal( - self.reader.decode(&data)?, + self.reader.decode(&data).map_err(map_xml_error)?, lang.take(), datatype.take(), )? @@ -378,10 +466,11 @@ impl ResultsIterator { ); } _ => { - return Err(Error::msg(format!( + return Err(invalid_data_error(format!( "Unexpected textual value found: {}", - self.reader.decode(&data)? - ))); + self.reader.decode(&data).map_err(map_xml_error)? + )) + .into()); } } } @@ -392,7 +481,9 @@ impl ResultsIterator { if let Some(var) = ¤t_var { new_bindings[self.mapping[var]] = term.clone() } else { - return Err(Error::msg("No name found for tag")); + return Err( + invalid_data_error("No name found for tag").into() + ); } term = None; state = State::Result; @@ -418,12 +509,22 @@ fn build_literal( value: impl Into, lang: Option, datatype: Option, -) -> Result { +) -> Result { match datatype { Some(datatype) => Ok(Literal::new_typed_literal(value, datatype)), None => match lang { - Some(lang) => Ok(Literal::new_language_tagged_literal(value, lang)?), + Some(lang) => Literal::new_language_tagged_literal(value, &lang).map_err(|e| { + invalid_data_error(format!("Invalid xml:lang value '{}': {}", lang, e)).into() + }), None => Ok(Literal::new_simple_literal(value)), }, } } + +fn map_xml_error(error: quick_xml::Error) -> EvaluationError { + match error { + quick_xml::Error::Io(error) => error, + _ => invalid_data_error(error), + } + .into() +} diff --git a/lib/src/store/memory.rs b/lib/src/store/memory.rs index 6e6deae6..65931a3a 100644 --- a/lib/src/store/memory.rs +++ b/lib/src/store/memory.rs @@ -3,12 +3,11 @@ use crate::error::{Infallible, UnwrapInfallible}; use crate::io::{DatasetFormat, GraphFormat}; use crate::model::*; -use crate::sparql::{Query, QueryOptions, QueryResult, SimplePreparedQuery}; +use crate::sparql::{EvaluationError, Query, QueryOptions, QueryResult, SimplePreparedQuery}; use crate::store::numeric_encoder::*; use crate::store::{ dump_dataset, dump_graph, load_dataset, load_graph, ReadableEncodedStore, WritableEncodedStore, }; -use crate::Error; use std::collections::hash_map::DefaultHasher; use std::collections::{HashMap, HashSet}; use std::convert::TryInto; @@ -45,7 +44,7 @@ use std::{fmt, io}; /// if let QueryResult::Solutions(mut solutions) = store.query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default())? { /// assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into())); /// } -/// # oxigraph::Result::Ok(()) +/// # Result::<_,Box>::Ok(()) /// ``` #[derive(Clone)] pub struct MemoryStore { @@ -102,13 +101,13 @@ impl MemoryStore { /// if let QueryResult::Solutions(mut solutions) = store.query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default())? { /// assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into())); /// } - /// # oxigraph::Result::Ok(()) + /// # Result::<_,Box>::Ok(()) /// ``` pub fn query( &self, - query: impl TryInto>, + query: impl TryInto>, options: QueryOptions, - ) -> crate::Result { + ) -> Result { self.prepare_query(query, options)?.exec() } @@ -132,13 +131,13 @@ impl MemoryStore { /// if let QueryResult::Solutions(mut solutions) = prepared_query.exec()? { /// assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into())); /// } - /// # oxigraph::Result::Ok(()) + /// # Result::<_,Box>::Ok(()) /// ``` pub fn prepare_query( &self, - query: impl TryInto>, + query: impl TryInto>, options: QueryOptions, - ) -> crate::Result { + ) -> Result { Ok(MemoryPreparedQuery(SimplePreparedQuery::new( self.clone(), query, @@ -163,7 +162,7 @@ impl MemoryStore { /// // quad filter /// let results: Vec = store.quads_for_pattern(None, None, None, None).collect(); /// assert_eq!(vec![quad], results); - /// # oxigraph::Result::Ok(()) + /// # Result::<_,Box>::Ok(()) /// ``` pub fn quads_for_pattern( &self, @@ -232,7 +231,7 @@ impl MemoryStore { /// /// // quad filter /// assert!(store.contains(&quad)); - /// # oxigraph::Result::Ok(()) + /// # Result::<_,Box>::Ok(()) /// ``` pub fn transaction<'a, E>( &'a self, @@ -266,7 +265,7 @@ impl MemoryStore { /// let results: Vec = store.quads_for_pattern(None, None, None, None).collect(); /// let ex = NamedNode::new("http://example.com")?; /// assert_eq!(vec![Quad::new(ex.clone(), ex.clone(), ex.clone(), None)], results); - /// # oxigraph::Result::Ok(()) + /// # Result::<_,Box>::Ok(()) /// ``` /// /// Errors related to parameter validation like the base IRI use the `INVALID_INPUT` error kind. @@ -300,7 +299,7 @@ impl MemoryStore { /// let results: Vec = store.quads_for_pattern(None, None, None, None).collect(); /// let ex = NamedNode::new("http://example.com")?; /// assert_eq!(vec![Quad::new(ex.clone(), ex.clone(), ex.clone(), Some(ex.into()))], results); - /// # oxigraph::Result::Ok(()) + /// # Result::<_,Box>::Ok(()) /// ``` /// /// Errors related to parameter validation like the base IRI use the `INVALID_INPUT` error kind. @@ -356,7 +355,7 @@ impl MemoryStore { /// let mut buffer = Vec::new(); /// store.dump_graph(&mut buffer, GraphFormat::NTriples, &GraphName::DefaultGraph)?; /// assert_eq!(file, buffer.as_slice()); - /// # oxigraph::Result::Ok(()) + /// # std::io::Result::Ok(()) /// ``` /// /// Errors related to parameter validation like the base IRI use the `INVALID_INPUT` error kind. @@ -390,7 +389,7 @@ impl MemoryStore { /// let mut buffer = Vec::new(); /// store.dump_dataset(&mut buffer, DatasetFormat::NQuads)?; /// assert_eq!(file, buffer.as_slice()); - /// # oxigraph::Result::Ok(()) + /// # std::io::Result::Ok(()) /// ``` /// /// Errors related to parameter validation like the base IRI use the `INVALID_INPUT` error kind. @@ -954,7 +953,7 @@ pub struct MemoryPreparedQuery(SimplePreparedQuery); impl MemoryPreparedQuery { /// Evaluates the query and returns its results - pub fn exec(&self) -> crate::Result { + pub fn exec(&self) -> Result { self.0.exec() } } @@ -992,7 +991,7 @@ impl<'a> MemoryTransaction<'a> { /// let results: Vec = store.quads_for_pattern(None, None, None, None).collect(); /// let ex = NamedNode::new("http://example.com")?; /// assert_eq!(vec![Quad::new(ex.clone(), ex.clone(), ex.clone(), None)], results); - /// # oxigraph::Result::Ok(()) + /// # Result::<_, oxigraph::sparql::EvaluationError>::Ok(()) /// ``` pub fn load_graph( &mut self, @@ -1022,7 +1021,7 @@ impl<'a> MemoryTransaction<'a> { /// let results: Vec = store.quads_for_pattern(None, None, None, None).collect(); /// let ex = NamedNode::new("http://example.com")?; /// assert_eq!(vec![Quad::new(ex.clone(), ex.clone(), ex.clone(), Some(ex.into()))], results); - /// # oxigraph::Result::Ok(()) + /// # Result::<_, oxigraph::sparql::EvaluationError>::Ok(()) /// ``` pub fn load_dataset( &mut self, diff --git a/lib/src/store/mod.rs b/lib/src/store/mod.rs index ba5377a2..4c0e507f 100644 --- a/lib/src/store/mod.rs +++ b/lib/src/store/mod.rs @@ -16,6 +16,7 @@ pub use crate::store::rocksdb::RocksDbStore; #[cfg(feature = "sled")] pub use crate::store::sled::SledStore; +use crate::error::{invalid_data_error, invalid_input_error}; use crate::io::{DatasetFormat, GraphFormat}; use crate::model::*; use crate::store::numeric_encoder::*; @@ -90,7 +91,7 @@ where IoOrParseError: From, P::Error: Send + Sync + 'static, { - let mut parser = parser.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?; + let mut parser = parser.map_err(invalid_input_error)?; let mut bnode_map = HashMap::default(); let to_graph_name = store .encode_graph_name(to_graph_name) @@ -163,7 +164,7 @@ where IoOrParseError: From, P::Error: Send + Sync + 'static, { - let mut parser = parser.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?; + let mut parser = parser.map_err(invalid_input_error)?; let mut bnode_map = HashMap::default(); let result: Result<(), IoOrParseError<_>> = parser.parse_all(&mut move |q| { let quad = store @@ -228,7 +229,7 @@ impl From> for io::E fn from(error: IoOrParseError) -> Self { match error { IoOrParseError::Io(error) => error, - IoOrParseError::Parse(error) => io::Error::new(io::ErrorKind::InvalidData, error), + IoOrParseError::Parse(error) => invalid_data_error(error), } } } diff --git a/lib/src/store/numeric_encoder.rs b/lib/src/store/numeric_encoder.rs index 9ba9d7c5..d07002c3 100644 --- a/lib/src/store/numeric_encoder.rs +++ b/lib/src/store/numeric_encoder.rs @@ -1,6 +1,6 @@ #![allow(clippy::unreadable_literal)] -use crate::error::{Infallible, UnwrapInfallible}; +use crate::error::{invalid_data_error, Infallible, UnwrapInfallible}; use crate::model::vocab::rdf; use crate::model::vocab::xsd; use crate::model::xsd::*; @@ -1321,10 +1321,6 @@ fn get_required_str(lookup: &impl StrLookup, id: StrHash) -> Result) -> io::Error { - io::Error::new(io::ErrorKind::InvalidData, msg.into()) -} - #[test] fn test_encoding() { let mut store = MemoryStrStore::default(); diff --git a/lib/src/store/rocksdb.rs b/lib/src/store/rocksdb.rs index bb86232c..ff40591a 100644 --- a/lib/src/store/rocksdb.rs +++ b/lib/src/store/rocksdb.rs @@ -1,9 +1,9 @@ //! Store based on the [RocksDB](https://rocksdb.org/) key-value database. -use crate::error::{Infallible, UnwrapInfallible}; +use crate::error::{invalid_data_error, Infallible, UnwrapInfallible}; use crate::io::{DatasetFormat, GraphFormat}; use crate::model::*; -use crate::sparql::{Query, QueryOptions, QueryResult, SimplePreparedQuery}; +use crate::sparql::{EvaluationError, Query, QueryOptions, QueryResult, SimplePreparedQuery}; use crate::store::numeric_encoder::*; use crate::store::{ dump_dataset, dump_graph, load_dataset, load_graph, ReadableEncodedStore, WritableEncodedStore, @@ -48,7 +48,7 @@ use std::{fmt, str}; /// # /// # }; /// # remove_dir_all("example.db")?; -/// # oxigraph::Result::Ok(()) +/// # Result::<_,Box>::Ok(()) /// ``` #[derive(Clone)] pub struct RocksDbStore { @@ -95,9 +95,9 @@ impl RocksDbStore { /// See `MemoryStore` for a usage example. pub fn query( &self, - query: impl TryInto>, + query: impl TryInto>, options: QueryOptions, - ) -> Result { + ) -> Result { self.prepare_query(query, options)?.exec() } @@ -107,9 +107,9 @@ impl RocksDbStore { /// See `MemoryStore` for a usage example. pub fn prepare_query( &self, - query: impl TryInto>, + query: impl TryInto>, options: QueryOptions, - ) -> Result { + ) -> Result { Ok(RocksDbPreparedQuery(SimplePreparedQuery::new( (*self).clone(), query, @@ -194,7 +194,7 @@ impl RocksDbStore { format: GraphFormat, to_graph_name: &GraphName, base_iri: Option<&str>, - ) -> Result<(), crate::Error> { + ) -> Result<(), io::Error> { let mut transaction = self.auto_batch_writer(); load_graph(&mut transaction, reader, format, to_graph_name, base_iri)?; Ok(transaction.apply()?) @@ -215,7 +215,7 @@ impl RocksDbStore { reader: impl BufRead, format: DatasetFormat, base_iri: Option<&str>, - ) -> Result<(), crate::Error> { + ) -> Result<(), io::Error> { let mut transaction = self.auto_batch_writer(); load_dataset(&mut transaction, reader, format, base_iri)?; Ok(transaction.apply()?) @@ -476,7 +476,7 @@ impl StrLookup for RocksDbStore { .map_err(map_err)? .map(String::from_utf8) .transpose() - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + .map_err(invalid_data_error) } } @@ -553,7 +553,7 @@ pub struct RocksDbPreparedQuery(SimplePreparedQuery); impl RocksDbPreparedQuery { /// Evaluates the query and returns its results - pub fn exec(&self) -> Result { + pub fn exec(&self) -> Result { self.0.exec() } } @@ -892,14 +892,14 @@ fn map_err(e: Error) -> io::Error { } #[test] -fn store() -> Result<(), crate::Error> { +fn store() -> Result<(), io::Error> { use crate::model::*; use rand::random; use std::env::temp_dir; use std::fs::remove_dir_all; let main_s = NamedOrBlankNode::from(BlankNode::default()); - let main_p = NamedNode::new("http://example.com")?; + let main_p = NamedNode::new("http://example.com").unwrap(); let main_o = Term::from(Literal::from(1)); let main_quad = Quad::new(main_s.clone(), main_p.clone(), main_o.clone(), None); diff --git a/lib/src/store/sled.rs b/lib/src/store/sled.rs index 43949cff..e7a3647e 100644 --- a/lib/src/store/sled.rs +++ b/lib/src/store/sled.rs @@ -1,9 +1,9 @@ //! Store based on the [Sled](https://sled.rs/) key-value database. -use crate::error::{Infallible, UnwrapInfallible}; +use crate::error::{invalid_data_error, Infallible, UnwrapInfallible}; use crate::io::{DatasetFormat, GraphFormat}; use crate::model::*; -use crate::sparql::{Query, QueryOptions, QueryResult, SimplePreparedQuery}; +use crate::sparql::{EvaluationError, Query, QueryOptions, QueryResult, SimplePreparedQuery}; use crate::store::numeric_encoder::*; use crate::store::{ dump_dataset, dump_graph, load_dataset, load_graph, ReadableEncodedStore, WritableEncodedStore, @@ -47,7 +47,7 @@ use std::{fmt, io, str}; /// # /// # }; /// # remove_dir_all("example.db")?; -/// # oxigraph::Result::Ok(()) +/// # Result::<_,Box>::Ok(()) /// ``` #[derive(Clone)] pub struct SledStore { @@ -90,9 +90,9 @@ impl SledStore { /// See `MemoryStore` for a usage example. pub fn query( &self, - query: impl TryInto>, + query: impl TryInto>, options: QueryOptions, - ) -> Result { + ) -> Result { self.prepare_query(query, options)?.exec() } @@ -101,9 +101,9 @@ impl SledStore { /// See `MemoryStore` for a usage example. pub fn prepare_query( &self, - query: impl TryInto>, + query: impl TryInto>, options: QueryOptions, - ) -> Result { + ) -> Result { Ok(SledPreparedQuery(SimplePreparedQuery::new( (*self).clone(), query, @@ -393,7 +393,7 @@ impl StrLookup for SledStore { .get(id.to_be_bytes())? .map(|v| String::from_utf8(v.to_vec())) .transpose() - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + .map_err(invalid_data_error) } } @@ -712,7 +712,7 @@ pub struct SledPreparedQuery(SimplePreparedQuery); impl SledPreparedQuery { /// Evaluates the query and returns its results - pub fn exec(&self) -> Result { + pub fn exec(&self) -> Result { self.0.exec() } } @@ -829,19 +829,19 @@ fn decode_quad(encoded: &[u8]) -> Result { GSPO_PREFIX => Ok(cursor.read_gspo_quad()?), GPOS_PREFIX => Ok(cursor.read_gpos_quad()?), GOSP_PREFIX => Ok(cursor.read_gosp_quad()?), - _ => Err(io::Error::new( - io::ErrorKind::InvalidData, - format!("Invalid quad type identifier: {}", encoded[0]), - )), + _ => Err(invalid_data_error(format!( + "Invalid quad type identifier: {}", + encoded[0] + ))), } } #[test] -fn store() -> Result<(), crate::Error> { +fn store() -> Result<(), io::Error> { use crate::model::*; let main_s = NamedOrBlankNode::from(BlankNode::default()); - let main_p = NamedNode::new("http://example.com")?; + let main_p = NamedNode::new("http://example.com").unwrap(); let main_o = Term::from(Literal::from(1)); let main_quad = Quad::new(main_s.clone(), main_p.clone(), main_o.clone(), None); diff --git a/python/src/memory_store.rs b/python/src/memory_store.rs index 5e9f88fa..7cd2d434 100644 --- a/python/src/memory_store.rs +++ b/python/src/memory_store.rs @@ -59,7 +59,7 @@ impl PyMemoryStore { let results = self .inner .query(query, QueryOptions::default()) - .map_err(|e| ValueError::py_err(e.to_string()))?; + .map_err(map_evaluation_error)?; query_results_to_python(py, results) } diff --git a/python/src/sled_store.rs b/python/src/sled_store.rs index d3b3fc5d..940fea9c 100644 --- a/python/src/sled_store.rs +++ b/python/src/sled_store.rs @@ -61,7 +61,7 @@ impl PySledStore { let results = self .inner .query(query, QueryOptions::default()) - .map_err(|e| ValueError::py_err(e.to_string()))?; + .map_err(map_evaluation_error)?; query_results_to_python(py, results) } diff --git a/python/src/store_utils.rs b/python/src/store_utils.rs index 011752f0..ff9b5e8c 100644 --- a/python/src/store_utils.rs +++ b/python/src/store_utils.rs @@ -1,7 +1,9 @@ use crate::model::*; use oxigraph::model::*; -use oxigraph::sparql::{QueryResult, QuerySolution, QuerySolutionsIterator, QueryTriplesIterator}; -use pyo3::exceptions::{IOError, TypeError, ValueError}; +use oxigraph::sparql::{ + EvaluationError, QueryResult, QuerySolution, QuerySolutionsIterator, QueryTriplesIterator, +}; +use pyo3::exceptions::{IOError, RuntimeError, TypeError, ValueError}; use pyo3::prelude::*; use pyo3::{PyIterProtocol, PyMappingProtocol, PyNativeType, PyObjectProtocol}; use std::fmt::Write; @@ -113,7 +115,7 @@ impl PyIterProtocol for QuerySolutionIter { .inner .next() .transpose() - .map_err(|e| IOError::py_err(e.to_string()))? //TODO: improve + .map_err(map_evaluation_error)? .map(move |inner| PyQuerySolution { inner })) } } @@ -134,7 +136,7 @@ impl PyIterProtocol for TripleResultIter { .inner .next() .transpose() - .map_err(|e| IOError::py_err(e.to_string()))? //TODO: improve + .map_err(map_evaluation_error)? .map(move |t| triple_to_python(slf.py(), t))) } } @@ -147,3 +149,12 @@ pub fn map_io_err(error: io::Error) -> PyErr { _ => IOError::py_err(error.to_string()), } } + +pub fn map_evaluation_error(error: EvaluationError) -> PyErr { + match error { + EvaluationError::Parsing(error) => ValueError::py_err(error.to_string()), + EvaluationError::Io(error) => map_io_err(error), + EvaluationError::Query(error) => ValueError::py_err(error.to_string()), + _ => RuntimeError::py_err(error.to_string()), + } +} diff --git a/testsuite/src/sparql_evaluator.rs b/testsuite/src/sparql_evaluator.rs index 6aac765b..cf37e461 100644 --- a/testsuite/src/sparql_evaluator.rs +++ b/testsuite/src/sparql_evaluator.rs @@ -7,11 +7,11 @@ use chrono::Utc; use oxigraph::model::vocab::*; use oxigraph::model::*; use oxigraph::sparql::*; -use oxigraph::{Error, MemoryStore}; +use oxigraph::MemoryStore; use std::collections::HashMap; -use std::fmt; use std::str::FromStr; use std::sync::Arc; +use std::{fmt, io}; pub fn evaluate_sparql_tests( manifest: impl Iterator>, @@ -168,10 +168,21 @@ impl StaticServiceHandler { } impl ServiceHandler for StaticServiceHandler { - fn handle(&self, service_name: NamedNode, query: Query) -> oxigraph::Result { + type Error = EvaluationError; + + fn handle( + &self, + service_name: NamedNode, + query: Query, + ) -> std::result::Result { self.services .get(&service_name) - .ok_or_else(|| Error::msg(format!("Service {} not found", service_name)))? + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + format!("Service {} not found", service_name), + ) + })? .query( query, QueryOptions::default().with_service_handler(self.clone()), @@ -183,7 +194,7 @@ fn to_dataset(result: QueryResult, with_order: bool) -> Result { match result { QueryResult::Graph(graph) => Ok(graph .map(|t| t.map(|t| t.in_graph(None))) - .collect::>()?), + .collect::>()?), QueryResult::Boolean(value) => { let store = MemoryStore::new(); let result_set = BlankNode::default();