diff --git a/Cargo.lock b/Cargo.lock
index fa61e235..50c7f71e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -998,7 +998,6 @@ dependencies = [
"predicates",
"rand",
"rayon-core",
- "sparesults",
"url",
]
diff --git a/lib/oxrdfio/README.md b/lib/oxrdfio/README.md
index 2bdd68d8..318c718c 100644
--- a/lib/oxrdfio/README.md
+++ b/lib/oxrdfio/README.md
@@ -21,27 +21,28 @@ Support for [SPARQL-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html
It is designed as a low level parser compatible with both synchronous and asynchronous I/O (behind the `async-tokio` feature).
-Usage example counting the number of people in a Turtle file:
+Usage example converting a Turtle file to a N-Triples file:
```rust
-use oxrdf::{NamedNodeRef, vocab::rdf};
-use oxrdfio::{RdfFormat, RdfParser};
+use oxrdfio::{RdfFormat, RdfParser, RdfSerializer};
-let file = b"@base .
+let turtle_file = b"@base .
@prefix schema: .
a schema:Person ;
schema:name \"Foo\" .
a schema:Person ;
schema:name \"Bar\" .";
-let schema_person = NamedNodeRef::new("http://schema.org/Person").unwrap();
-let mut count = 0;
-for quad in RdfParser::from_format(RdfFormat::Turtle).parse_read(file.as_ref()) {
- let quad = quad.unwrap();
- if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
- count += 1;
- }
+let ntriples_file = b" .
+ \"Foo\" .
+ .
+ \"Bar\" .
+";
+
+let mut writer = RdfSerializer::from_format(RdfFormat::NTriples).serialize_to_write(Vec::new());
+for quad in RdfParser::from_format(RdfFormat::Turtle).parse_read(turtle_file.as_ref()) {
+ writer.write_quad(&quad.unwrap()).unwrap();
}
-assert_eq!(2, count);
+assert_eq!(writer.finish().unwrap(), ntriples_file);
```
## License
diff --git a/lib/src/io/mod.rs b/lib/src/io/mod.rs
index f183157d..2e4dd2f4 100644
--- a/lib/src/io/mod.rs
+++ b/lib/src/io/mod.rs
@@ -1,4 +1,28 @@
-//! Utilities to read and write RDF graphs and datasets.
+//! Utilities to read and write RDF graphs and datasets using [OxRDF I/O](https://crates.io/crates/oxrdfio).
+//!
+//! Usage example converting a Turtle file to a N-Triples file:
+//! ```
+//! use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer};
+//!
+//! let turtle_file = b"@base .
+//! @prefix schema: .
+//! a schema:Person ;
+//! schema:name \"Foo\" .
+//! a schema:Person ;
+//! schema:name \"Bar\" .";
+//!
+//! let ntriples_file = b" .
+//! \"Foo\" .
+//! .
+//! \"Bar\" .
+//! ";
+//!
+//! let mut writer = RdfSerializer::from_format(RdfFormat::NTriples).serialize_to_write(Vec::new());
+//! for quad in RdfParser::from_format(RdfFormat::Turtle).parse_read(turtle_file.as_ref()) {
+//! writer.write_quad(&quad.unwrap()).unwrap();
+//! }
+//! assert_eq!(writer.finish().unwrap(), ntriples_file);
+//! ```
mod format;
pub mod read;
diff --git a/lib/src/lib.rs b/lib/src/lib.rs
index e5a680d8..b36c4d6c 100644
--- a/lib/src/lib.rs
+++ b/lib/src/lib.rs
@@ -6,17 +6,7 @@
#![doc(html_logo_url = "https://raw.githubusercontent.com/oxigraph/oxigraph/main/logo.svg")]
pub mod io;
+pub mod model;
pub mod sparql;
mod storage;
pub mod store;
-
-pub mod model {
- //! Implements data structures for [RDF 1.1 Concepts](https://www.w3.org/TR/rdf11-concepts/) using [OxRDF](https://crates.io/crates/oxrdf).
-
- pub use oxrdf::{
- dataset, graph, vocab, BlankNode, BlankNodeIdParseError, BlankNodeRef, Dataset, Graph,
- GraphName, GraphNameRef, IriParseError, LanguageTagParseError, Literal, LiteralRef,
- NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, QuadRef, Subject,
- SubjectRef, Term, TermParseError, TermRef, Triple, TripleRef,
- };
-}
diff --git a/lib/src/model.rs b/lib/src/model.rs
new file mode 100644
index 00000000..ef8cbd1d
--- /dev/null
+++ b/lib/src/model.rs
@@ -0,0 +1,25 @@
+//! Implements data structures for [RDF 1.1 Concepts](https://www.w3.org/TR/rdf11-concepts/) using [OxRDF](https://crates.io/crates/oxrdf).
+//!
+//! Usage example:
+//!
+//! ```
+//! use oxigraph::model::*;
+//!
+//! let mut graph = Graph::default();
+//!
+//! // insertion
+//! let ex = NamedNodeRef::new("http://example.com").unwrap();
+//! let triple = TripleRef::new(ex, ex, ex);
+//! graph.insert(triple);
+//!
+//! // simple filter
+//! let results: Vec<_> = graph.triples_for_subject(ex).collect();
+//! assert_eq!(vec![triple], results);
+//! ```
+
+pub use oxrdf::{
+ dataset, graph, vocab, BlankNode, BlankNodeIdParseError, BlankNodeRef, Dataset, Graph,
+ GraphName, GraphNameRef, IriParseError, LanguageTagParseError, Literal, LiteralRef, NamedNode,
+ NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, QuadRef, Subject, SubjectRef, Term,
+ TermParseError, TermRef, Triple, TripleRef,
+};
diff --git a/lib/src/sparql/error.rs b/lib/src/sparql/error.rs
index 62e5d821..4728efb7 100644
--- a/lib/src/sparql/error.rs
+++ b/lib/src/sparql/error.rs
@@ -1,7 +1,10 @@
-use crate::io::{ParseError, SyntaxError};
+use crate::io::ParseError as RdfParseError;
+use crate::model::NamedNode;
+use crate::sparql::results::ParseError as ResultsParseError;
+use crate::sparql::ParseError;
use crate::storage::StorageError;
use std::convert::Infallible;
-use std::error;
+use std::error::Error;
use std::fmt;
use std::io;
@@ -10,29 +13,31 @@ use std::io;
#[non_exhaustive]
pub enum EvaluationError {
/// An error in SPARQL parsing.
- Parsing(spargebra::ParseError),
+ Parsing(ParseError),
/// An error from the storage.
Storage(StorageError),
/// An error while parsing an external RDF file.
- GraphParsing(SyntaxError),
+ GraphParsing(RdfParseError),
/// An error while parsing an external result file (likely from a federated query).
- ResultsParsing(sparesults::ParseError),
- /// An error returned during store or results I/Os.
- Io(io::Error),
- /// An error returned during the query evaluation itself (not supported custom function...).
- Query(QueryError),
-}
-
-/// An error returned during the query evaluation itself (not supported custom function...).
-#[derive(Debug)]
-pub struct QueryError {
- inner: QueryErrorKind,
-}
-
-#[derive(Debug)]
-enum QueryErrorKind {
- Msg { msg: String },
- Other(Box),
+ ResultsParsing(ResultsParseError),
+ /// An error returned during results serialization.
+ ResultsSerialization(io::Error),
+ /// Error during `SERVICE` evaluation
+ Service(Box),
+ /// Error when `CREATE` tries to create an already existing graph
+ GraphAlreadyExists(NamedNode),
+ /// Error when `DROP` or `CLEAR` tries to remove a not existing graph
+ GraphDoesNotExist(NamedNode),
+ /// The variable storing the `SERVICE` name is unbound
+ UnboundService,
+ /// The given `SERVICE` is not supported
+ UnsupportedService(NamedNode),
+ /// The given content media type returned from an HTTP response is not supported (`SERVICE` and `LOAD`)
+ UnsupportedContentType(String),
+ /// The `SERVICE` call has not returns solutions
+ ServiceDoesNotReturnSolutions,
+ /// The results are not a RDF graph
+ NotAGraph,
}
impl fmt::Display for EvaluationError {
@@ -43,64 +48,50 @@ impl fmt::Display for EvaluationError {
Self::Storage(error) => error.fmt(f),
Self::GraphParsing(error) => error.fmt(f),
Self::ResultsParsing(error) => error.fmt(f),
- Self::Io(error) => error.fmt(f),
- Self::Query(error) => error.fmt(f),
+ Self::ResultsSerialization(error) => error.fmt(f),
+ Self::Service(error) => error.fmt(f),
+ Self::GraphAlreadyExists(graph) => write!(f, "The graph {graph} already exists"),
+ Self::GraphDoesNotExist(graph) => write!(f, "The graph {graph} does not exist"),
+ Self::UnboundService => write!(f, "The variable encoding the service name is unbound"),
+ Self::UnsupportedService(service) => {
+ write!(f, "The service {service} is not supported")
+ }
+ Self::UnsupportedContentType(content_type) => {
+ write!(f, "The content media type {content_type} is not supported")
+ }
+ Self::ServiceDoesNotReturnSolutions => write!(
+ f,
+ "The service is not returning solutions but a boolean or a graph"
+ ),
+ Self::NotAGraph => write!(f, "The query results are not a RDF graph"),
}
}
}
-impl fmt::Display for QueryError {
+impl Error for EvaluationError {
#[inline]
- 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 {
- #[inline]
- fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Parsing(e) => Some(e),
Self::Storage(e) => Some(e),
Self::GraphParsing(e) => Some(e),
Self::ResultsParsing(e) => Some(e),
- Self::Io(e) => Some(e),
- Self::Query(e) => Some(e),
- }
- }
-}
-
-impl error::Error for QueryError {
- #[inline]
- fn source(&self) -> Option<&(dyn error::Error + 'static)> {
- match &self.inner {
- QueryErrorKind::Msg { .. } => None,
- QueryErrorKind::Other(e) => Some(e.as_ref()),
+ Self::ResultsSerialization(e) => Some(e),
+ Self::Service(e) => {
+ let e = Box::as_ref(e);
+ Some(e)
+ }
+ Self::GraphAlreadyExists(_)
+ | Self::GraphDoesNotExist(_)
+ | Self::UnboundService
+ | Self::UnsupportedService(_)
+ | Self::UnsupportedContentType(_)
+ | Self::ServiceDoesNotReturnSolutions
+ | Self::NotAGraph => None,
}
}
}
-impl EvaluationError {
- /// Wraps another error.
- #[inline]
- 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.
- #[inline]
- pub(crate) fn msg(msg: impl Into) -> Self {
- Self::Query(QueryError {
- inner: QueryErrorKind::Msg { msg: msg.into() },
- })
- }
-}
-
impl From for EvaluationError {
#[inline]
fn from(error: Infallible) -> Self {
@@ -108,9 +99,9 @@ impl From for EvaluationError {
}
}
-impl From for EvaluationError {
+impl From for EvaluationError {
#[inline]
- fn from(error: spargebra::ParseError) -> Self {
+ fn from(error: ParseError) -> Self {
Self::Parsing(error)
}
}
@@ -122,26 +113,16 @@ impl From for EvaluationError {
}
}
-impl From for EvaluationError {
+impl From for EvaluationError {
#[inline]
- fn from(error: io::Error) -> Self {
- Self::Io(error)
- }
-}
-
-impl From for EvaluationError {
- #[inline]
- fn from(error: ParseError) -> Self {
- match error {
- ParseError::Syntax(error) => Self::GraphParsing(error),
- ParseError::Io(error) => Self::Io(error),
- }
+ fn from(error: RdfParseError) -> Self {
+ Self::GraphParsing(error)
}
}
-impl From for EvaluationError {
+impl From for EvaluationError {
#[inline]
- fn from(error: sparesults::ParseError) -> Self {
+ fn from(error: ResultsParseError) -> Self {
Self::ResultsParsing(error)
}
}
@@ -153,9 +134,19 @@ impl From for io::Error {
EvaluationError::Parsing(error) => Self::new(io::ErrorKind::InvalidData, error),
EvaluationError::GraphParsing(error) => error.into(),
EvaluationError::ResultsParsing(error) => error.into(),
- EvaluationError::Io(error) => error,
+ EvaluationError::ResultsSerialization(error) => error,
EvaluationError::Storage(error) => error.into(),
- EvaluationError::Query(error) => Self::new(io::ErrorKind::Other, error),
+ EvaluationError::Service(error) => match error.downcast() {
+ Ok(error) => *error,
+ Err(error) => Self::new(io::ErrorKind::Other, error),
+ },
+ EvaluationError::GraphAlreadyExists(_)
+ | EvaluationError::GraphDoesNotExist(_)
+ | EvaluationError::UnboundService
+ | EvaluationError::UnsupportedService(_)
+ | EvaluationError::UnsupportedContentType(_)
+ | EvaluationError::ServiceDoesNotReturnSolutions
+ | EvaluationError::NotAGraph => Self::new(io::ErrorKind::InvalidInput, error),
}
}
}
diff --git a/lib/src/sparql/eval.rs b/lib/src/sparql/eval.rs
index fb1737d6..ed9feba7 100644
--- a/lib/src/sparql/eval.rs
+++ b/lib/src/sparql/eval.rs
@@ -1077,7 +1077,7 @@ impl SimpleEvaluator {
) -> Result {
let service_name = service_name
.get_pattern_value(from)
- .ok_or_else(|| EvaluationError::msg("The SERVICE name is not bound"))?;
+ .ok_or(EvaluationError::UnboundService)?;
if let QueryResults::Solutions(iter) = self.service_handler.handle(
self.dataset.decode_named_node(&service_name)?,
Query {
@@ -1092,9 +1092,7 @@ impl SimpleEvaluator {
)? {
Ok(encode_bindings(Rc::clone(&self.dataset), variables, iter))
} else {
- Err(EvaluationError::msg(
- "The service call has not returned a set of solutions",
- ))
+ Err(EvaluationError::ServiceDoesNotReturnSolutions)
}
}
diff --git a/lib/src/sparql/mod.rs b/lib/src/sparql/mod.rs
index 95249203..6d3ef593 100644
--- a/lib/src/sparql/mod.rs
+++ b/lib/src/sparql/mod.rs
@@ -8,13 +8,14 @@ mod error;
mod eval;
mod http;
mod model;
+pub mod results;
mod service;
mod update;
use crate::model::{NamedNode, Term};
pub use crate::sparql::algebra::{Query, QueryDataset, Update};
use crate::sparql::dataset::DatasetView;
-pub use crate::sparql::error::{EvaluationError, QueryError};
+pub use crate::sparql::error::EvaluationError;
use crate::sparql::eval::{EvalNodeWithStats, SimpleEvaluator, Timer};
pub use crate::sparql::model::{QueryResults, QuerySolution, QuerySolutionIter, QueryTripleIter};
pub use crate::sparql::service::ServiceHandler;
@@ -24,7 +25,6 @@ use crate::storage::StorageReader;
use json_event_parser::{JsonEvent, JsonWriter};
pub use oxrdf::{Variable, VariableNameParseError};
use oxsdatatypes::{DayTimeDuration, Float};
-pub use sparesults::QueryResultsFormat;
pub use spargebra::ParseError;
use sparopt::algebra::GraphPattern;
use sparopt::Optimizer;
diff --git a/lib/src/sparql/model.rs b/lib/src/sparql/model.rs
index d076baec..71a25ea9 100644
--- a/lib/src/sparql/model.rs
+++ b/lib/src/sparql/model.rs
@@ -1,12 +1,12 @@
use crate::io::{RdfFormat, RdfSerializer};
use crate::model::*;
use crate::sparql::error::EvaluationError;
-use oxrdf::{Variable, VariableRef};
-pub use sparesults::QuerySolution;
-use sparesults::{
+use crate::sparql::results::{
ParseError, QueryResultsFormat, QueryResultsParser, QueryResultsReader, QueryResultsSerializer,
SolutionsReader,
};
+use oxrdf::{Variable, VariableRef};
+pub use sparesults::QuerySolution;
use std::io::{BufRead, Write};
use std::rc::Rc;
@@ -38,7 +38,7 @@ impl QueryResults {
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::model::*;
- /// use oxigraph::sparql::QueryResultsFormat;
+ /// use oxigraph::sparql::results::QueryResultsFormat;
///
/// let store = Store::new()?;
/// let ex = NamedNodeRef::new("http://example.com")?;
@@ -57,33 +57,43 @@ impl QueryResults {
let serializer = QueryResultsSerializer::from_format(format);
match self {
Self::Boolean(value) => {
- serializer.write_boolean_result(writer, value)?;
+ serializer
+ .write_boolean_result(writer, value)
+ .map_err(EvaluationError::ResultsSerialization)?;
}
Self::Solutions(solutions) => {
- let mut writer =
- serializer.solutions_writer(writer, solutions.variables().to_vec())?;
+ let mut writer = serializer
+ .solutions_writer(writer, solutions.variables().to_vec())
+ .map_err(EvaluationError::ResultsSerialization)?;
for solution in solutions {
- writer.write(&solution?)?;
+ writer
+ .write(&solution?)
+ .map_err(EvaluationError::ResultsSerialization)?;
}
- writer.finish()?;
+ writer
+ .finish()
+ .map_err(EvaluationError::ResultsSerialization)?;
}
Self::Graph(triples) => {
let s = VariableRef::new_unchecked("subject");
let p = VariableRef::new_unchecked("predicate");
let o = VariableRef::new_unchecked("object");
- let mut writer = serializer.solutions_writer(
- writer,
- vec![s.into_owned(), p.into_owned(), o.into_owned()],
- )?;
+ let mut writer = serializer
+ .solutions_writer(writer, vec![s.into_owned(), p.into_owned(), o.into_owned()])
+ .map_err(EvaluationError::ResultsSerialization)?;
for triple in triples {
let triple = triple?;
- writer.write([
- (s, &triple.subject.into()),
- (p, &triple.predicate.into()),
- (o, &triple.object),
- ])?;
+ writer
+ .write([
+ (s, &triple.subject.into()),
+ (p, &triple.predicate.into()),
+ (o, &triple.object),
+ ])
+ .map_err(EvaluationError::ResultsSerialization)?;
}
- writer.finish()?;
+ writer
+ .finish()
+ .map_err(EvaluationError::ResultsSerialization)?;
}
}
Ok(())
@@ -116,14 +126,16 @@ impl QueryResults {
if let Self::Graph(triples) = self {
let mut writer = RdfSerializer::from_format(format.into()).serialize_to_write(write);
for triple in triples {
- writer.write_triple(&triple?)?;
+ writer
+ .write_triple(&triple?)
+ .map_err(EvaluationError::ResultsSerialization)?;
}
- writer.finish()?;
+ writer
+ .finish()
+ .map_err(EvaluationError::ResultsSerialization)?;
Ok(())
} else {
- Err(EvaluationError::msg(
- "Bindings or booleans could not be formatted as an RDF graph",
- ))
+ Err(EvaluationError::NotAGraph)
}
}
}
diff --git a/lib/src/sparql/results.rs b/lib/src/sparql/results.rs
new file mode 100644
index 00000000..26fa287a
--- /dev/null
+++ b/lib/src/sparql/results.rs
@@ -0,0 +1,47 @@
+//! Utilities to read and write RDF results formats using [sparesults](https://crates.io/crates/sparesults).
+//!
+//! It supports [SPARQL Query Results XML Format (Second Edition)](https://www.w3.org/TR/rdf-sparql-XMLres/), [SPARQL 1.1 Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/) and [SPARQL 1.1 Query Results CSV and TSV Formats](https://www.w3.org/TR/sparql11-results-csv-tsv/).
+//!
+//! Usage example converting a JSON result file into a TSV result file:
+//!
+//! ```
+//! use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsParser, QueryResultsReader, QueryResultsSerializer};
+//! use std::io::Result;
+//!
+//! fn convert_json_to_tsv(json_file: &[u8]) -> Result> {
+//! let json_parser = QueryResultsParser::from_format(QueryResultsFormat::Json);
+//! let tsv_serializer = QueryResultsSerializer::from_format(QueryResultsFormat::Tsv);
+//! // We start to read the JSON file and see which kind of results it is
+//! match json_parser.read_results(json_file)? {
+//! QueryResultsReader::Boolean(value) => {
+//! // it's a boolean result, we copy it in TSV to the output buffer
+//! tsv_serializer.write_boolean_result(Vec::new(), value)
+//! },
+//! QueryResultsReader::Solutions(solutions_reader) => {
+//! // it's a set of solutions, we create a writer and we write to it while reading in streaming from the JSON file
+//! let mut solutions_writer = tsv_serializer.solutions_writer(Vec::new(), solutions_reader.variables().to_vec())?;
+//! for solution in solutions_reader {
+//! solutions_writer.write(&solution?)?;
+//! }
+//! solutions_writer.finish()
+//! }
+//! }
+//! }
+//!
+//! // Let's test with a boolean
+//! assert_eq!(
+//! convert_json_to_tsv(b"{\"boolean\":true}".as_slice()).unwrap(),
+//! b"true"
+//! );
+//!
+//! // And with a set of solutions
+//! assert_eq!(
+//! convert_json_to_tsv(b"{\"head\":{\"vars\":[\"foo\",\"bar\"]},\"results\":{\"bindings\":[{\"foo\":{\"type\":\"literal\",\"value\":\"test\"}}]}}".as_slice()).unwrap(),
+//! b"?foo\t?bar\n\"test\"\t\n"
+//! );
+//! ```
+
+pub use sparesults::{
+ ParseError, QueryResultsFormat, QueryResultsParser, QueryResultsReader, QueryResultsSerializer,
+ SolutionsReader, SyntaxError,
+};
diff --git a/lib/src/sparql/service.rs b/lib/src/sparql/service.rs
index 3d6576e2..ae397ee2 100644
--- a/lib/src/sparql/service.rs
+++ b/lib/src/sparql/service.rs
@@ -3,7 +3,7 @@ use crate::sparql::algebra::Query;
use crate::sparql::error::EvaluationError;
use crate::sparql::http::Client;
use crate::sparql::model::QueryResults;
-use crate::sparql::QueryResultsFormat;
+use crate::sparql::results::QueryResultsFormat;
use std::error::Error;
use std::io::BufReader;
use std::time::Duration;
@@ -61,10 +61,8 @@ pub struct EmptyServiceHandler;
impl ServiceHandler for EmptyServiceHandler {
type Error = EvaluationError;
- fn handle(&self, _: NamedNode, _: Query) -> Result {
- Err(EvaluationError::msg(
- "The SERVICE feature is not implemented",
- ))
+ fn handle(&self, name: NamedNode, _: Query) -> Result {
+ Err(EvaluationError::UnsupportedService(name))
}
}
@@ -88,7 +86,7 @@ impl ServiceHandler for ErrorConversionServiceHandler {
) -> Result {
self.handler
.handle(service_name, query)
- .map_err(EvaluationError::wrap)
+ .map_err(|e| EvaluationError::Service(Box::new(e)))
}
}
@@ -112,17 +110,17 @@ impl ServiceHandler for SimpleServiceHandler {
service_name: NamedNode,
query: Query,
) -> Result {
- let (content_type, body) = self.client.post(
- service_name.as_str(),
- query.to_string().into_bytes(),
- "application/sparql-query",
- "application/sparql-results+json, application/sparql-results+xml",
- )?;
- let format = QueryResultsFormat::from_media_type(&content_type).ok_or_else(|| {
- EvaluationError::msg(format!(
- "Unsupported Content-Type returned by {service_name}: {content_type}"
- ))
- })?;
+ let (content_type, body) = self
+ .client
+ .post(
+ service_name.as_str(),
+ query.to_string().into_bytes(),
+ "application/sparql-query",
+ "application/sparql-results+json, application/sparql-results+xml",
+ )
+ .map_err(|e| EvaluationError::Service(Box::new(e)))?;
+ let format = QueryResultsFormat::from_media_type(&content_type)
+ .ok_or_else(|| EvaluationError::UnsupportedContentType(content_type))?;
Ok(QueryResults::read(BufReader::new(body), format)?)
}
}
diff --git a/lib/src/sparql/update.rs b/lib/src/sparql/update.rs
index ef4f9c54..1744c464 100644
--- a/lib/src/sparql/update.rs
+++ b/lib/src/sparql/update.rs
@@ -17,6 +17,7 @@ use spargebra::term::{
use spargebra::GraphUpdateOperation;
use sparopt::Optimizer;
use std::collections::HashMap;
+use std::io;
use std::rc::Rc;
pub fn evaluate_update<'a, 'b: 'a>(
@@ -71,9 +72,7 @@ impl<'a, 'b: 'a> SimpleUpdateEvaluator<'a, 'b> {
} => self.eval_delete_insert(
delete,
insert,
- using_dataset
- .as_ref()
- .ok_or_else(|| EvaluationError::msg("No dataset"))?,
+ using_dataset.as_ref().unwrap_or(&QueryDataset::new()),
pattern,
),
GraphUpdateOperation::Load {
@@ -161,15 +160,15 @@ impl<'a, 'b: 'a> SimpleUpdateEvaluator<'a, 'b> {
}
fn eval_load(&mut self, from: &NamedNode, to: &GraphName) -> Result<(), EvaluationError> {
- let (content_type, body) = self.client.get(
- from.as_str(),
- "application/n-triples, text/turtle, application/rdf+xml",
- )?;
- let format = RdfFormat::from_media_type(&content_type).ok_or_else(|| {
- EvaluationError::msg(format!(
- "Unsupported Content-Type returned by {from}: {content_type}"
- ))
- })?;
+ let (content_type, body) = self
+ .client
+ .get(
+ from.as_str(),
+ "application/n-triples, text/turtle, application/rdf+xml",
+ )
+ .map_err(|e| EvaluationError::Service(Box::new(e)))?;
+ let format = RdfFormat::from_media_type(&content_type)
+ .ok_or_else(|| EvaluationError::UnsupportedContentType(content_type))?;
let to_graph_name = match to {
GraphName::NamedNode(graph_name) => graph_name.into(),
GraphName::DefaultGraph => GraphNameRef::DefaultGraph,
@@ -178,11 +177,12 @@ impl<'a, 'b: 'a> SimpleUpdateEvaluator<'a, 'b> {
.rename_blank_nodes()
.without_named_graphs()
.with_default_graph(to_graph_name);
- if let Some(base_iri) = &self.base_iri {
- parser = parser.with_base_iri(base_iri.as_str()).map_err(|e| {
- EvaluationError::msg(format!("The LOAD IRI '{base_iri}' is invalid: {e}"))
- })?;
- }
+ parser = parser.with_base_iri(from.as_str()).map_err(|e| {
+ EvaluationError::Service(Box::new(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ format!("Invalid URL: {from}: {e}"),
+ )))
+ })?;
for q in parser.parse_read(body) {
self.transaction.insert(q?.as_ref())?;
}
@@ -193,9 +193,7 @@ impl<'a, 'b: 'a> SimpleUpdateEvaluator<'a, 'b> {
if self.transaction.insert_named_graph(graph_name.into())? || silent {
Ok(())
} else {
- Err(EvaluationError::msg(format!(
- "The graph {graph_name} already exists"
- )))
+ Err(EvaluationError::GraphAlreadyExists(graph_name.clone()))
}
}
@@ -211,9 +209,7 @@ impl<'a, 'b: 'a> SimpleUpdateEvaluator<'a, 'b> {
} else if silent {
Ok(())
} else {
- Err(EvaluationError::msg(format!(
- "The graph {graph} does not exists"
- )))
+ Err(EvaluationError::GraphDoesNotExist(graph_name.clone()))
}
}
GraphTarget::DefaultGraph => {
@@ -231,9 +227,7 @@ impl<'a, 'b: 'a> SimpleUpdateEvaluator<'a, 'b> {
if self.transaction.remove_named_graph(graph_name.into())? || silent {
Ok(())
} else {
- Err(EvaluationError::msg(format!(
- "The graph {graph_name} does not exists"
- )))
+ Err(EvaluationError::GraphDoesNotExist(graph_name.clone()))
}
}
GraphTarget::DefaultGraph => {
diff --git a/python/src/sparql.rs b/python/src/sparql.rs
index 408f6f0c..7c99707b 100644
--- a/python/src/sparql.rs
+++ b/python/src/sparql.rs
@@ -233,9 +233,21 @@ pub fn map_evaluation_error(error: EvaluationError) -> PyErr {
match error {
EvaluationError::Parsing(error) => PySyntaxError::new_err(error.to_string()),
EvaluationError::Storage(error) => map_storage_error(error),
- EvaluationError::Io(error) => map_io_err(error),
- EvaluationError::GraphParsing(error) => PySyntaxError::new_err(error.to_string()),
- EvaluationError::Query(error) => PyValueError::new_err(error.to_string()),
+ EvaluationError::GraphParsing(error) => match error {
+ oxigraph::io::ParseError::Syntax(error) => PySyntaxError::new_err(error.to_string()),
+ oxigraph::io::ParseError::Io(error) => map_io_err(error),
+ },
+ EvaluationError::ResultsParsing(error) => match error {
+ oxigraph::sparql::results::ParseError::Syntax(error) => {
+ PySyntaxError::new_err(error.to_string())
+ }
+ oxigraph::sparql::results::ParseError::Io(error) => map_io_err(error),
+ },
+ EvaluationError::ResultsSerialization(error) => map_io_err(error),
+ EvaluationError::Service(error) => match error.downcast() {
+ Ok(error) => map_io_err(*error),
+ Err(error) => PyRuntimeError::new_err(error.to_string()),
+ },
_ => PyRuntimeError::new_err(error.to_string()),
}
}
diff --git a/server/Cargo.toml b/server/Cargo.toml
index 01ed8a77..350863f9 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -19,7 +19,6 @@ anyhow = "1"
oxhttp = { version = "0.1", features = ["rayon"] }
clap = { version = "4", features = ["derive"] }
oxigraph = { version = "0.4.0-alpha.1-dev", path = "../lib", features = ["http_client"] }
-sparesults = { version = "0.2.0-alpha.1-dev", path = "../lib/sparesults", features = ["rdf-star"] }
rand = "0.8"
url = "2"
oxiri = "0.2"
diff --git a/server/src/main.rs b/server/src/main.rs
index e32a1fe7..817fed04 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -8,12 +8,12 @@ use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer};
use oxigraph::model::{
GraphName, GraphNameRef, IriParseError, NamedNode, NamedNodeRef, NamedOrBlankNode,
};
+use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsSerializer};
use oxigraph::sparql::{Query, QueryOptions, QueryResults, Update};
use oxigraph::store::{BulkLoader, LoaderError, Store};
use oxiri::Iri;
use rand::random;
use rayon_core::ThreadPoolBuilder;
-use sparesults::{QueryResultsFormat, QueryResultsSerializer};
use std::borrow::Cow;
use std::cell::RefCell;
use std::cmp::{max, min};
diff --git a/testsuite/src/sparql_evaluator.rs b/testsuite/src/sparql_evaluator.rs
index 318da172..543f0538 100644
--- a/testsuite/src/sparql_evaluator.rs
+++ b/testsuite/src/sparql_evaluator.rs
@@ -6,6 +6,7 @@ use crate::vocab::*;
use anyhow::{anyhow, bail, Result};
use oxigraph::model::vocab::*;
use oxigraph::model::*;
+use oxigraph::sparql::results::QueryResultsFormat;
use oxigraph::sparql::*;
use oxigraph::store::Store;
use sparopt::Optimizer;
@@ -339,10 +340,10 @@ impl ServiceHandler for StaticServiceHandler {
self.services
.get(&service_name)
.ok_or_else(|| {
- io::Error::new(
+ EvaluationError::Service(Box::new(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Service {service_name} not found"),
- )
+ )))
})?
.query_opt(
query,