Exposes SPARQL results I/O in Oxigraph and improve EvaluationError

pull/617/head
Tpt 1 year ago committed by Thomas Tanon
parent 9da26c6f95
commit 7fe055d2b4
  1. 1
      Cargo.lock
  2. 25
      lib/oxrdfio/README.md
  3. 26
      lib/src/io/mod.rs
  4. 12
      lib/src/lib.rs
  5. 25
      lib/src/model.rs
  6. 159
      lib/src/sparql/error.rs
  7. 6
      lib/src/sparql/eval.rs
  8. 4
      lib/src/sparql/mod.rs
  9. 60
      lib/src/sparql/model.rs
  10. 47
      lib/src/sparql/results.rs
  11. 32
      lib/src/sparql/service.rs
  12. 46
      lib/src/sparql/update.rs
  13. 18
      python/src/sparql.rs
  14. 1
      server/Cargo.toml
  15. 2
      server/src/main.rs
  16. 5
      testsuite/src/sparql_evaluator.rs

1
Cargo.lock generated

@ -998,7 +998,6 @@ dependencies = [
"predicates",
"rand",
"rayon-core",
"sparesults",
"url",
]

@ -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 <http://example.com/> .
let turtle_file = b"@base <http://example.com/> .
@prefix schema: <http://schema.org/> .
<foo> a schema:Person ;
schema:name \"Foo\" .
<bar> a schema:Person ;
schema:name \"Bar\" .";
let 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"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
<http://example.com/foo> <http://schema.org/name> \"Foo\" .
<http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
<http://example.com/bar> <http://schema.org/name> \"Bar\" .
";
let mut writer = RdfSerializer::from_format(RdfFormat::NTriples).serialize_to_write(Vec::new());
for quad in RdfParser::from_format(RdfFormat::Turtle).parse_read(turtle_file.as_ref()) {
writer.write_quad(&quad.unwrap()).unwrap();
}
assert_eq!(2, count);
assert_eq!(writer.finish().unwrap(), ntriples_file);
```
## License

@ -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 <http://example.com/> .
//! @prefix schema: <http://schema.org/> .
//! <foo> a schema:Person ;
//! schema:name \"Foo\" .
//! <bar> a schema:Person ;
//! schema:name \"Bar\" .";
//!
//! let ntriples_file = b"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
//! <http://example.com/foo> <http://schema.org/name> \"Foo\" .
//! <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
//! <http://example.com/bar> <http://schema.org/name> \"Bar\" .
//! ";
//!
//! let mut writer = RdfSerializer::from_format(RdfFormat::NTriples).serialize_to_write(Vec::new());
//! for quad in RdfParser::from_format(RdfFormat::Turtle).parse_read(turtle_file.as_ref()) {
//! writer.write_quad(&quad.unwrap()).unwrap();
//! }
//! assert_eq!(writer.finish().unwrap(), ntriples_file);
//! ```
mod format;
pub mod read;

@ -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,
};
}

@ -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,
};

@ -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<dyn error::Error + Send + Sync + 'static>),
ResultsParsing(ResultsParseError),
/// An error returned during results serialization.
ResultsSerialization(io::Error),
/// Error during `SERVICE` evaluation
Service(Box<dyn Error + Send + Sync + 'static>),
/// 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<String>) -> Self {
Self::Query(QueryError {
inner: QueryErrorKind::Msg { msg: msg.into() },
})
}
}
impl From<Infallible> for EvaluationError {
#[inline]
fn from(error: Infallible) -> Self {
@ -108,9 +99,9 @@ impl From<Infallible> for EvaluationError {
}
}
impl From<spargebra::ParseError> for EvaluationError {
impl From<ParseError> for EvaluationError {
#[inline]
fn from(error: spargebra::ParseError) -> Self {
fn from(error: ParseError) -> Self {
Self::Parsing(error)
}
}
@ -122,26 +113,16 @@ impl From<StorageError> for EvaluationError {
}
}
impl From<io::Error> for EvaluationError {
impl From<RdfParseError> for EvaluationError {
#[inline]
fn from(error: io::Error) -> Self {
Self::Io(error)
}
}
impl From<ParseError> 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<sparesults::ParseError> for EvaluationError {
impl From<ResultsParseError> for EvaluationError {
#[inline]
fn from(error: sparesults::ParseError) -> Self {
fn from(error: ResultsParseError) -> Self {
Self::ResultsParsing(error)
}
}
@ -153,9 +134,19 @@ impl From<EvaluationError> 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),
}
}
}

@ -1077,7 +1077,7 @@ impl SimpleEvaluator {
) -> Result<EncodedTuplesIterator, EvaluationError> {
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)
}
}

@ -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;

@ -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)
}
}
}

@ -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<Vec<u8>> {
//! let json_parser = QueryResultsParser::from_format(QueryResultsFormat::Json);
//! let tsv_serializer = QueryResultsSerializer::from_format(QueryResultsFormat::Tsv);
//! // We start to read the JSON file and see which kind of results it is
//! match json_parser.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,
};

@ -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<QueryResults, EvaluationError> {
Err(EvaluationError::msg(
"The SERVICE feature is not implemented",
))
fn handle(&self, name: NamedNode, _: Query) -> Result<QueryResults, EvaluationError> {
Err(EvaluationError::UnsupportedService(name))
}
}
@ -88,7 +86,7 @@ impl<S: ServiceHandler> ServiceHandler for ErrorConversionServiceHandler<S> {
) -> Result<QueryResults, EvaluationError> {
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<QueryResults, EvaluationError> {
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)?)
}
}

@ -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 => {

@ -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()),
}
}

@ -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"

@ -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};

@ -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,

Loading…
Cancel
Save