Adopt new I/O API for serialization

pull/592/head
Tpt 1 year ago committed by Thomas Tanon
parent 7cd383af79
commit 217abaf7ee
  1. 30
      js/src/store.rs
  2. 1
      lib/src/io/mod.rs
  3. 4
      lib/src/io/write.rs
  4. 11
      lib/src/sparql/error.rs
  5. 13
      lib/src/sparql/model.rs
  6. 21
      lib/src/sparql/update.rs
  7. 12
      lib/src/storage/error.rs
  8. 24
      lib/src/store.rs
  9. 10
      lib/tests/store.rs
  10. 45
      python/src/io.rs
  11. 4
      python/src/sparql.rs
  12. 36
      python/src/store.rs
  13. 4
      python/tests/test_store.py
  14. 154
      server/src/main.rs

@ -4,7 +4,7 @@ use crate::format_err;
use crate::model::*; use crate::model::*;
use crate::utils::to_err; use crate::utils::to_err;
use js_sys::{Array, Map}; use js_sys::{Array, Map};
use oxigraph::io::{DatasetFormat, GraphFormat}; use oxigraph::io::{DatasetFormat, GraphFormat, RdfFormat};
use oxigraph::model::*; use oxigraph::model::*;
use oxigraph::sparql::QueryResults; use oxigraph::sparql::QueryResults;
use oxigraph::store::Store; use oxigraph::store::Store;
@ -191,34 +191,22 @@ impl JsStore {
} }
pub fn dump(&self, mime_type: &str, from_graph_name: &JsValue) -> Result<String, JsValue> { pub fn dump(&self, mime_type: &str, from_graph_name: &JsValue) -> Result<String, JsValue> {
let Some(format) = RdfFormat::from_media_type(mime_type) else {
return Err(format_err!("Not supported MIME type: {mime_type}"));
};
let from_graph_name = let from_graph_name =
if let Some(graph_name) = FROM_JS.with(|c| c.to_optional_term(from_graph_name))? { if let Some(graph_name) = FROM_JS.with(|c| c.to_optional_term(from_graph_name))? {
Some(graph_name.try_into()?) Some(GraphName::try_from(graph_name)?)
} else { } else {
None None
}; };
let mut buffer = Vec::new(); let mut buffer = Vec::new();
if let Some(graph_format) = GraphFormat::from_media_type(mime_type) { if let Some(from_graph_name) = &from_graph_name {
self.store self.store.dump_graph(&mut buffer, format, from_graph_name)
.dump_graph(
&mut buffer,
graph_format,
&from_graph_name.unwrap_or(GraphName::DefaultGraph),
)
.map_err(to_err)?;
} else if let Some(dataset_format) = DatasetFormat::from_media_type(mime_type) {
if from_graph_name.is_some() {
return Err(format_err!(
"The target graph name parameter is not available for dataset formats"
));
}
self.store
.dump_dataset(&mut buffer, dataset_format)
.map_err(to_err)?;
} else { } else {
return Err(format_err!("Not supported MIME type: {mime_type}")); self.store.dump_dataset(&mut buffer, format)
} }
.map_err(to_err)?;
String::from_utf8(buffer).map_err(to_err) String::from_utf8(buffer).map_err(to_err)
} }
} }

@ -7,6 +7,7 @@ pub mod write;
pub use self::format::{DatasetFormat, GraphFormat}; pub use self::format::{DatasetFormat, GraphFormat};
pub use self::read::{DatasetParser, GraphParser}; pub use self::read::{DatasetParser, GraphParser};
#[allow(deprecated)]
pub use self::write::{DatasetSerializer, GraphSerializer}; pub use self::write::{DatasetSerializer, GraphSerializer};
pub use oxrdfio::{ pub use oxrdfio::{
FromReadQuadReader, ParseError, RdfFormat, RdfParser, RdfSerializer, SyntaxError, FromReadQuadReader, ParseError, RdfFormat, RdfParser, RdfSerializer, SyntaxError,

@ -1,3 +1,5 @@
#![allow(deprecated)]
//! Utilities to write RDF graphs and datasets. //! Utilities to write RDF graphs and datasets.
use crate::io::{DatasetFormat, GraphFormat}; use crate::io::{DatasetFormat, GraphFormat};
@ -28,6 +30,7 @@ use std::io::{self, Write};
/// assert_eq!(buffer.as_slice(), "<http://example.com/s> <http://example.com/p> <http://example.com/o> .\n".as_bytes()); /// assert_eq!(buffer.as_slice(), "<http://example.com/s> <http://example.com/p> <http://example.com/o> .\n".as_bytes());
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_,Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
#[deprecated(note = "Use RdfSerializer instead")]
pub struct GraphSerializer { pub struct GraphSerializer {
inner: RdfSerializer, inner: RdfSerializer,
} }
@ -110,6 +113,7 @@ impl<W: Write> TripleWriter<W> {
/// assert_eq!(buffer.as_slice(), "<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n".as_bytes()); /// assert_eq!(buffer.as_slice(), "<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n".as_bytes());
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_,Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
#[deprecated(note = "Use RdfSerializer instead")]
pub struct DatasetSerializer { pub struct DatasetSerializer {
inner: RdfSerializer, inner: RdfSerializer,
} }

@ -1,4 +1,4 @@
use crate::io::read::ParseError; use crate::io::{ParseError, SyntaxError};
use crate::storage::StorageError; use crate::storage::StorageError;
use std::convert::Infallible; use std::convert::Infallible;
use std::error; use std::error;
@ -14,10 +14,10 @@ pub enum EvaluationError {
/// An error from the storage. /// An error from the storage.
Storage(StorageError), Storage(StorageError),
/// An error while parsing an external RDF file. /// An error while parsing an external RDF file.
GraphParsing(ParseError), GraphParsing(SyntaxError),
/// An error while parsing an external result file (likely from a federated query). /// An error while parsing an external result file (likely from a federated query).
ResultsParsing(sparesults::ParseError), ResultsParsing(sparesults::ParseError),
/// An error returned during store IOs or during results write. /// An error returned during store or results I/Os.
Io(io::Error), Io(io::Error),
/// An error returned during the query evaluation itself (not supported custom function...). /// An error returned during the query evaluation itself (not supported custom function...).
Query(QueryError), Query(QueryError),
@ -132,7 +132,10 @@ impl From<io::Error> for EvaluationError {
impl From<ParseError> for EvaluationError { impl From<ParseError> for EvaluationError {
#[inline] #[inline]
fn from(error: ParseError) -> Self { fn from(error: ParseError) -> Self {
Self::GraphParsing(error) match error {
ParseError::Syntax(error) => Self::GraphParsing(error),
ParseError::Io(error) => Self::Io(error),
}
} }
} }

@ -1,5 +1,4 @@
use crate::io::GraphFormat; use crate::io::{RdfFormat, RdfSerializer};
use crate::io::GraphSerializer;
use crate::model::*; use crate::model::*;
use crate::sparql::error::EvaluationError; use crate::sparql::error::EvaluationError;
use oxrdf::{Variable, VariableRef}; use oxrdf::{Variable, VariableRef};
@ -96,7 +95,7 @@ impl QueryResults {
/// ///
/// ``` /// ```
/// use oxigraph::store::Store; /// use oxigraph::store::Store;
/// use oxigraph::io::GraphFormat; /// use oxigraph::io::{RdfFormat, GraphFormat};
/// use oxigraph::model::*; /// use oxigraph::model::*;
/// ///
/// let graph = "<http://example.com> <http://example.com> <http://example.com> .\n"; /// let graph = "<http://example.com> <http://example.com> <http://example.com> .\n";
@ -105,19 +104,19 @@ impl QueryResults {
/// store.load_graph(graph.as_bytes(), GraphFormat::NTriples, GraphNameRef::DefaultGraph, None)?; /// store.load_graph(graph.as_bytes(), GraphFormat::NTriples, GraphNameRef::DefaultGraph, None)?;
/// ///
/// let mut results = Vec::new(); /// let mut results = Vec::new();
/// store.query("CONSTRUCT WHERE { ?s ?p ?o }")?.write_graph(&mut results, GraphFormat::NTriples)?; /// store.query("CONSTRUCT WHERE { ?s ?p ?o }")?.write_graph(&mut results, RdfFormat::NTriples)?;
/// assert_eq!(results, graph.as_bytes()); /// assert_eq!(results, graph.as_bytes());
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_,Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
pub fn write_graph( pub fn write_graph(
self, self,
write: impl Write, write: impl Write,
format: GraphFormat, format: impl Into<RdfFormat>,
) -> Result<(), EvaluationError> { ) -> Result<(), EvaluationError> {
if let Self::Graph(triples) = self { if let Self::Graph(triples) = self {
let mut writer = GraphSerializer::from_format(format).triple_writer(write); let mut writer = RdfSerializer::from_format(format.into()).serialize_to_write(write);
for triple in triples { for triple in triples {
writer.write(&triple?)?; writer.write_triple(&triple?)?;
} }
writer.finish()?; writer.finish()?;
Ok(()) Ok(())

@ -1,5 +1,4 @@
use crate::io::read::ParseError; use crate::io::{RdfFormat, RdfParser};
use crate::io::{GraphFormat, GraphParser};
use crate::model::{GraphName as OxGraphName, GraphNameRef, Quad as OxQuad}; use crate::model::{GraphName as OxGraphName, GraphNameRef, Quad as OxQuad};
use crate::sparql::algebra::QueryDataset; use crate::sparql::algebra::QueryDataset;
use crate::sparql::dataset::DatasetView; use crate::sparql::dataset::DatasetView;
@ -166,7 +165,7 @@ impl<'a, 'b: 'a> SimpleUpdateEvaluator<'a, 'b> {
from.as_str(), from.as_str(),
"application/n-triples, text/turtle, application/rdf+xml", "application/n-triples, text/turtle, application/rdf+xml",
)?; )?;
let format = GraphFormat::from_media_type(&content_type).ok_or_else(|| { let format = RdfFormat::from_media_type(&content_type).ok_or_else(|| {
EvaluationError::msg(format!( EvaluationError::msg(format!(
"Unsupported Content-Type returned by {from}: {content_type}" "Unsupported Content-Type returned by {from}: {content_type}"
)) ))
@ -175,15 +174,17 @@ impl<'a, 'b: 'a> SimpleUpdateEvaluator<'a, 'b> {
GraphName::NamedNode(graph_name) => graph_name.into(), GraphName::NamedNode(graph_name) => graph_name.into(),
GraphName::DefaultGraph => GraphNameRef::DefaultGraph, GraphName::DefaultGraph => GraphNameRef::DefaultGraph,
}; };
let mut parser = GraphParser::from_format(format); let mut parser = RdfParser::from_format(format)
.rename_blank_nodes()
.without_named_graphs()
.with_default_graph(to_graph_name);
if let Some(base_iri) = &self.base_iri { if let Some(base_iri) = &self.base_iri {
parser = parser parser = parser.with_base_iri(base_iri.as_str()).map_err(|e| {
.with_base_iri(base_iri.as_str()) EvaluationError::msg(format!("The LOAD IRI '{base_iri}' is invalid: {e}"))
.map_err(|e| ParseError::invalid_base_iri(base_iri, e))?; })?;
} }
for t in parser.read_triples(body) { for q in parser.parse_read(body) {
self.transaction self.transaction.insert(q?.as_ref())?;
.insert(t?.as_ref().in_graph(to_graph_name))?;
} }
Ok(()) Ok(())
} }

@ -1,4 +1,4 @@
use crate::io::read::ParseError; use crate::io::{read::ParseError, RdfFormat};
use std::error::Error; use std::error::Error;
use std::fmt; use std::fmt;
use std::io; use std::io;
@ -179,6 +179,8 @@ pub enum SerializerError {
Io(io::Error), Io(io::Error),
/// An error raised during the lookup in the store. /// An error raised during the lookup in the store.
Storage(StorageError), Storage(StorageError),
/// A format compatible with [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset) is required.
DatasetFormatExpected(RdfFormat),
} }
impl fmt::Display for SerializerError { impl fmt::Display for SerializerError {
@ -187,6 +189,10 @@ impl fmt::Display for SerializerError {
match self { match self {
Self::Io(e) => e.fmt(f), Self::Io(e) => e.fmt(f),
Self::Storage(e) => e.fmt(f), Self::Storage(e) => e.fmt(f),
Self::DatasetFormatExpected(format) => write!(
f,
"A RDF format supporting datasets was expected, {format} found"
),
} }
} }
} }
@ -197,6 +203,7 @@ impl Error for SerializerError {
match self { match self {
Self::Io(e) => Some(e), Self::Io(e) => Some(e),
Self::Storage(e) => Some(e), Self::Storage(e) => Some(e),
Self::DatasetFormatExpected(_) => None,
} }
} }
} }
@ -221,6 +228,9 @@ impl From<SerializerError> for io::Error {
match error { match error {
SerializerError::Storage(error) => error.into(), SerializerError::Storage(error) => error.into(),
SerializerError::Io(error) => error, SerializerError::Io(error) => error,
SerializerError::DatasetFormatExpected(_) => {
io::Error::new(io::ErrorKind::InvalidInput, error.to_string())
}
} }
} }
} }

@ -24,9 +24,7 @@
//! # Result::<_, Box<dyn std::error::Error>>::Ok(()) //! # Result::<_, Box<dyn std::error::Error>>::Ok(())
//! ``` //! ```
use crate::io::read::ParseError; use crate::io::read::ParseError;
use crate::io::{ use crate::io::{DatasetFormat, DatasetParser, GraphFormat, GraphParser, RdfFormat, RdfSerializer};
DatasetFormat, DatasetParser, DatasetSerializer, GraphFormat, GraphParser, GraphSerializer,
};
use crate::model::*; use crate::model::*;
use crate::sparql::{ use crate::sparql::{
evaluate_query, evaluate_update, EvaluationError, Query, QueryExplanation, QueryOptions, evaluate_query, evaluate_update, EvaluationError, Query, QueryExplanation, QueryOptions,
@ -612,13 +610,13 @@ impl Store {
/// ``` /// ```
pub fn dump_graph<'a>( pub fn dump_graph<'a>(
&self, &self,
writer: impl Write, write: impl Write,
format: GraphFormat, format: impl Into<RdfFormat>,
from_graph_name: impl Into<GraphNameRef<'a>>, from_graph_name: impl Into<GraphNameRef<'a>>,
) -> Result<(), SerializerError> { ) -> Result<(), SerializerError> {
let mut writer = GraphSerializer::from_format(format).triple_writer(writer); let mut writer = RdfSerializer::from_format(format.into()).serialize_to_write(write);
for quad in self.quads_for_pattern(None, None, None, Some(from_graph_name.into())) { for quad in self.quads_for_pattern(None, None, None, Some(from_graph_name.into())) {
writer.write(quad?.as_ref())?; writer.write_triple(quad?.as_ref())?;
} }
writer.finish()?; writer.finish()?;
Ok(()) Ok(())
@ -642,12 +640,16 @@ impl Store {
/// ``` /// ```
pub fn dump_dataset( pub fn dump_dataset(
&self, &self,
writer: impl Write, write: impl Write,
format: DatasetFormat, format: impl Into<RdfFormat>,
) -> Result<(), SerializerError> { ) -> Result<(), SerializerError> {
let mut writer = DatasetSerializer::from_format(format).quad_writer(writer); let format = format.into();
if !format.supports_datasets() {
return Err(SerializerError::DatasetFormatExpected(format));
}
let mut writer = RdfSerializer::from_format(format).serialize_to_write(write);
for quad in self.iter() { for quad in self.iter() {
writer.write(&quad?)?; writer.write_quad(&quad?)?;
} }
writer.finish()?; writer.finish()?;
Ok(()) Ok(())

@ -1,4 +1,4 @@
use oxigraph::io::{DatasetFormat, GraphFormat}; use oxigraph::io::{DatasetFormat, GraphFormat, RdfFormat};
use oxigraph::model::vocab::{rdf, xsd}; use oxigraph::model::vocab::{rdf, xsd};
use oxigraph::model::*; use oxigraph::model::*;
use oxigraph::store::Store; use oxigraph::store::Store;
@ -211,11 +211,7 @@ fn test_dump_graph() -> Result<(), Box<dyn Error>> {
} }
let mut buffer = Vec::new(); let mut buffer = Vec::new();
store.dump_graph( store.dump_graph(&mut buffer, RdfFormat::NTriples, GraphNameRef::DefaultGraph)?;
&mut buffer,
GraphFormat::NTriples,
GraphNameRef::DefaultGraph,
)?;
assert_eq!( assert_eq!(
buffer.into_iter().filter(|c| *c == b'\n').count(), buffer.into_iter().filter(|c| *c == b'\n').count(),
NUMBER_OF_TRIPLES NUMBER_OF_TRIPLES
@ -231,7 +227,7 @@ fn test_dump_dataset() -> Result<(), Box<dyn Error>> {
} }
let mut buffer = Vec::new(); let mut buffer = Vec::new();
store.dump_dataset(&mut buffer, DatasetFormat::NQuads)?; store.dump_dataset(&mut buffer, RdfFormat::NQuads)?;
assert_eq!( assert_eq!(
buffer.into_iter().filter(|c| *c == b'\n').count(), buffer.into_iter().filter(|c| *c == b'\n').count(),
NUMBER_OF_TRIPLES NUMBER_OF_TRIPLES

@ -3,8 +3,9 @@
use crate::model::{PyQuad, PyTriple}; use crate::model::{PyQuad, PyTriple};
use oxigraph::io::read::{ParseError, QuadReader, TripleReader}; use oxigraph::io::read::{ParseError, QuadReader, TripleReader};
use oxigraph::io::{ use oxigraph::io::{
DatasetFormat, DatasetParser, DatasetSerializer, GraphFormat, GraphParser, GraphSerializer, DatasetFormat, DatasetParser, GraphFormat, GraphParser, RdfFormat, RdfSerializer,
}; };
use oxigraph::model::QuadRef;
use pyo3::exceptions::{PyIOError, PySyntaxError, PyValueError}; use pyo3::exceptions::{PyIOError, PySyntaxError, PyValueError};
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::PyBytes; use pyo3::types::PyBytes;
@ -120,34 +121,34 @@ pub fn parse(
/// b'<http://example.com> <http://example.com/p> "1" .\n' /// b'<http://example.com> <http://example.com/p> "1" .\n'
#[pyfunction] #[pyfunction]
pub fn serialize(input: &PyAny, output: PyObject, mime_type: &str, py: Python<'_>) -> PyResult<()> { pub fn serialize(input: &PyAny, output: PyObject, mime_type: &str, py: Python<'_>) -> PyResult<()> {
let Some(format) = RdfFormat::from_media_type(mime_type) else {
return Err(PyValueError::new_err(format!(
"Not supported MIME type: {mime_type}"
)));
};
let output = if let Ok(path) = output.extract::<PathBuf>(py) { let output = if let Ok(path) = output.extract::<PathBuf>(py) {
PyWritable::from_file(&path, py).map_err(map_io_err)? PyWritable::from_file(&path, py).map_err(map_io_err)?
} else { } else {
PyWritable::from_data(output) PyWritable::from_data(output)
}; };
if let Some(graph_format) = GraphFormat::from_media_type(mime_type) { let mut writer = RdfSerializer::from_format(format).serialize_to_write(output);
let mut writer = GraphSerializer::from_format(graph_format).triple_writer(output); for i in input.iter()? {
for i in input.iter()? { let i = i?;
writer if let Ok(triple) = i.extract::<PyRef<PyTriple>>() {
.write(&*i?.extract::<PyRef<PyTriple>>()?) writer.write_triple(&*triple)
.map_err(map_io_err)?; } else {
} let quad = i.extract::<PyRef<PyQuad>>()?;
writer.finish().map_err(map_io_err)?; let quad = QuadRef::from(&*quad);
Ok(()) if !quad.graph_name.is_default_graph() && !format.supports_datasets() {
} else if let Some(dataset_format) = DatasetFormat::from_media_type(mime_type) { return Err(PyValueError::new_err(
let mut writer = DatasetSerializer::from_format(dataset_format).quad_writer(output); "The {format} format does not support named graphs",
for i in input.iter()? { ));
writer }
.write(&*i?.extract::<PyRef<PyQuad>>()?) writer.write_quad(quad)
.map_err(map_io_err)?;
} }
writer.finish().map_err(map_io_err)?; .map_err(map_io_err)?;
Ok(())
} else {
Err(PyValueError::new_err(format!(
"Not supported MIME type: {mime_type}"
)))
} }
writer.finish().map_err(map_io_err)
} }
#[pyclass(name = "TripleReader", module = "pyoxigraph")] #[pyclass(name = "TripleReader", module = "pyoxigraph")]

@ -1,4 +1,4 @@
use crate::io::{allow_threads_unsafe, map_io_err, map_parse_error}; use crate::io::{allow_threads_unsafe, map_io_err};
use crate::map_storage_error; use crate::map_storage_error;
use crate::model::*; use crate::model::*;
use oxigraph::model::Term; use oxigraph::model::Term;
@ -234,7 +234,7 @@ pub fn map_evaluation_error(error: EvaluationError) -> PyErr {
EvaluationError::Parsing(error) => PySyntaxError::new_err(error.to_string()), EvaluationError::Parsing(error) => PySyntaxError::new_err(error.to_string()),
EvaluationError::Storage(error) => map_storage_error(error), EvaluationError::Storage(error) => map_storage_error(error),
EvaluationError::Io(error) => map_io_err(error), EvaluationError::Io(error) => map_io_err(error),
EvaluationError::GraphParsing(error) => map_parse_error(error), EvaluationError::GraphParsing(error) => PySyntaxError::new_err(error.to_string()),
EvaluationError::Query(error) => PyValueError::new_err(error.to_string()), EvaluationError::Query(error) => PyValueError::new_err(error.to_string()),
_ => PyRuntimeError::new_err(error.to_string()), _ => PyRuntimeError::new_err(error.to_string()),
} }

@ -3,7 +3,7 @@
use crate::io::{allow_threads_unsafe, map_io_err, map_parse_error, PyReadable, PyWritable}; use crate::io::{allow_threads_unsafe, map_io_err, map_parse_error, PyReadable, PyWritable};
use crate::model::*; use crate::model::*;
use crate::sparql::*; use crate::sparql::*;
use oxigraph::io::{DatasetFormat, GraphFormat}; use oxigraph::io::{DatasetFormat, GraphFormat, RdfFormat};
use oxigraph::model::{GraphName, GraphNameRef}; use oxigraph::model::{GraphName, GraphNameRef};
use oxigraph::sparql::Update; use oxigraph::sparql::Update;
use oxigraph::store::{self, LoaderError, SerializerError, StorageError, Store}; use oxigraph::store::{self, LoaderError, SerializerError, StorageError, Store};
@ -522,10 +522,10 @@ impl PyStore {
/// :type output: io(bytes) or str or pathlib.Path /// :type output: io(bytes) or str or pathlib.Path
/// :param mime_type: the MIME type of the RDF serialization. /// :param mime_type: the MIME type of the RDF serialization.
/// :type mime_type: str /// :type mime_type: str
/// :param from_graph: if a triple based format is requested, the store graph from which dump the triples. By default, the default graph is used. /// :param from_graph: the store graph from which dump the triples. Required if the serialization format does not support named graphs. If it does supports named graphs the full dataset is written.
/// :type from_graph: NamedNode or BlankNode or DefaultGraph or None, optional /// :type from_graph: NamedNode or BlankNode or DefaultGraph or None, optional
/// :rtype: None /// :rtype: None
/// :raises ValueError: if the MIME type is not supported or the `from_graph` parameter is given with a quad syntax. /// :raises ValueError: if the MIME type is not supported or the `from_graph` parameter is not given with a syntax not supporting named graphs.
/// :raises IOError: if an I/O error happens during a quad lookup /// :raises IOError: if an I/O error happens during a quad lookup
/// ///
/// >>> store = Store() /// >>> store = Store()
@ -547,34 +547,23 @@ impl PyStore {
} else { } else {
PyWritable::from_data(output) PyWritable::from_data(output)
}; };
let Some(format) = RdfFormat::from_media_type(mime_type) else {
return Err(PyValueError::new_err(format!(
"Not supported MIME type: {mime_type}"
)));
};
let from_graph_name = if let Some(graph_name) = from_graph { let from_graph_name = if let Some(graph_name) = from_graph {
Some(GraphName::from(&PyGraphNameRef::try_from(graph_name)?)) Some(GraphName::from(&PyGraphNameRef::try_from(graph_name)?))
} else { } else {
None None
}; };
py.allow_threads(|| { py.allow_threads(|| {
if let Some(graph_format) = GraphFormat::from_media_type(mime_type) { if let Some(from_graph_name) = &from_graph_name {
self.inner self.inner.dump_graph(output, format, from_graph_name)
.dump_graph(
output,
graph_format,
&from_graph_name.unwrap_or(GraphName::DefaultGraph),
)
.map_err(map_serializer_error)
} else if let Some(dataset_format) = DatasetFormat::from_media_type(mime_type) {
if from_graph_name.is_some() {
return Err(PyValueError::new_err(
"The target graph name parameter is not available for dataset formats",
));
}
self.inner
.dump_dataset(output, dataset_format)
.map_err(map_serializer_error)
} else { } else {
Err(PyValueError::new_err(format!( self.inner.dump_dataset(output, format)
"Not supported MIME type: {mime_type}"
)))
} }
.map_err(map_serializer_error)
}) })
} }
@ -878,6 +867,7 @@ pub fn map_serializer_error(error: SerializerError) -> PyErr {
match error { match error {
SerializerError::Storage(error) => map_storage_error(error), SerializerError::Storage(error) => map_storage_error(error),
SerializerError::Io(error) => PyIOError::new_err(error.to_string()), SerializerError::Io(error) => PyIOError::new_err(error.to_string()),
SerializerError::DatasetFormatExpected(_) => PyValueError::new_err(error.to_string()),
} }
} }

@ -321,8 +321,10 @@ class TestStore(unittest.TestCase):
) )
def test_dump_with_io_error(self) -> None: def test_dump_with_io_error(self) -> None:
store = Store()
store.add(Quad(foo, bar, bar))
with self.assertRaises(OSError) as _, TemporaryFile("rb") as fp: with self.assertRaises(OSError) as _, TemporaryFile("rb") as fp:
Store().dump(fp, mime_type="application/rdf+xml") store.dump(fp, mime_type="application/trig")
def test_write_in_read(self) -> None: def test_write_in_read(self) -> None:
store = Store() store = Store()

@ -1,10 +1,10 @@
#![allow(clippy::print_stderr, clippy::cast_precision_loss, clippy::use_debug)] #![allow(clippy::print_stderr, clippy::cast_precision_loss, clippy::use_debug)]
use anyhow::{anyhow, bail, Context, Error}; use anyhow::{anyhow, bail, ensure, Context, Error};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use flate2::read::MultiGzDecoder; use flate2::read::MultiGzDecoder;
use oxhttp::model::{Body, HeaderName, HeaderValue, Method, Request, Response, Status}; use oxhttp::model::{Body, HeaderName, HeaderValue, Method, Request, Response, Status};
use oxhttp::Server; use oxhttp::Server;
use oxigraph::io::{DatasetFormat, DatasetSerializer, GraphFormat, GraphSerializer}; use oxigraph::io::{DatasetFormat, GraphFormat, RdfFormat, RdfSerializer};
use oxigraph::model::{ use oxigraph::model::{
GraphName, GraphNameRef, IriParseError, NamedNode, NamedNodeRef, NamedOrBlankNode, GraphName, GraphNameRef, IriParseError, NamedNode, NamedNodeRef, NamedOrBlankNode,
}; };
@ -424,9 +424,9 @@ pub fn main() -> anyhow::Result<()> {
.ok_or_else(|| anyhow!("The --location argument is required"))?, .ok_or_else(|| anyhow!("The --location argument is required"))?,
)?; )?;
let format = if let Some(format) = format { let format = if let Some(format) = format {
GraphOrDatasetFormat::from_str(&format)? rdf_format_from_name(&format)?
} else if let Some(file) = &file { } else if let Some(file) = &file {
GraphOrDatasetFormat::from_path(file)? rdf_format_from_path(file)?
} else { } else {
bail!("The --format option must be set when writing to stdout") bail!("The --format option must be set when writing to stdout")
}; };
@ -554,34 +554,25 @@ pub fn main() -> anyhow::Result<()> {
} }
} }
QueryResults::Graph(triples) => { QueryResults::Graph(triples) => {
let format = if let Some(name) = results_format { let format = if let Some(name) = &results_format {
if let Some(format) = GraphFormat::from_extension(&name) { rdf_format_from_name(name)
format
} else if let Some(format) = GraphFormat::from_media_type(&name) {
format
} else {
bail!("The file format '{name}' is unknown")
}
} else if let Some(results_file) = &results_file { } else if let Some(results_file) = &results_file {
format_from_path(results_file, |ext| { rdf_format_from_path(results_file)
GraphFormat::from_extension(ext)
.ok_or_else(|| anyhow!("The file extension '{ext}' is unknown"))
})?
} else { } else {
bail!("The --results-format option must be set when writing to stdout") bail!("The --results-format option must be set when writing to stdout")
}; }?;
let serializer = RdfSerializer::from_format(format);
if let Some(results_file) = results_file { if let Some(results_file) = results_file {
let mut writer = GraphSerializer::from_format(format) let mut writer = serializer
.triple_writer(BufWriter::new(File::create(results_file)?)); .serialize_to_write(BufWriter::new(File::create(results_file)?));
for triple in triples { for triple in triples {
writer.write(triple?.as_ref())?; writer.write_triple(triple?.as_ref())?;
} }
writer.finish()?; writer.finish()?;
} else { } else {
let mut writer = let mut writer = serializer.serialize_to_write(stdout().lock());
GraphSerializer::from_format(format).triple_writer(stdout().lock());
for triple in triples { for triple in triples {
writer.write(triple?.as_ref())?; writer.write_triple(triple?.as_ref())?;
} }
writer.finish()?; writer.finish()?;
} }
@ -670,22 +661,15 @@ fn bulk_load(
fn dump( fn dump(
store: &Store, store: &Store,
writer: impl Write, writer: impl Write,
format: GraphOrDatasetFormat, format: RdfFormat,
to_graph_name: Option<GraphName>, to_graph_name: Option<GraphName>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
match format { ensure!(format.supports_datasets() || to_graph_name.is_some(), "The --graph option is required when writing a format not supporting datasets like NTriples, Turtle or RDF/XML");
GraphOrDatasetFormat::Graph(format) => store.dump_graph( if let Some(to_graph_name) = &to_graph_name {
writer, store.dump_graph(writer, format, to_graph_name)
format, } else {
&to_graph_name.ok_or_else(|| anyhow!("The --graph option is required when writing a graph format like NTriples, Turtle or RDF/XML"))?, store.dump_dataset(writer, format)
)?, }?;
GraphOrDatasetFormat::Dataset(format) => {
if to_graph_name.is_some() {
bail!("The --graph option is not allowed when writing a dataset format like NQuads or TriG");
}
store.dump_dataset(writer, format)?
}
}
Ok(()) Ok(())
} }
@ -761,6 +745,23 @@ impl FromStr for GraphOrDatasetFormat {
} }
} }
fn rdf_format_from_path(path: &Path) -> anyhow::Result<RdfFormat> {
format_from_path(path, |ext| {
RdfFormat::from_extension(ext)
.ok_or_else(|| anyhow!("The file extension '{ext}' is unknown"))
})
}
fn rdf_format_from_name(name: &str) -> anyhow::Result<RdfFormat> {
if let Some(t) = RdfFormat::from_extension(name) {
return Ok(t);
}
if let Some(t) = RdfFormat::from_media_type(name) {
return Ok(t);
}
bail!("The file format '{name}' is unknown")
}
fn serve(store: Store, bind: String, read_only: bool, cors: bool) -> anyhow::Result<()> { fn serve(store: Store, bind: String, read_only: bool, cors: bool) -> anyhow::Result<()> {
let mut server = if cors { let mut server = if cors {
Server::new(cors_middleware(move |request| { Server::new(cors_middleware(move |request| {
@ -917,8 +918,9 @@ fn handle_request(
(path, "GET") if path.starts_with("/store") => { (path, "GET") if path.starts_with("/store") => {
if let Some(target) = store_target(request)? { if let Some(target) = store_target(request)? {
assert_that_graph_exists(&store, &target)?; assert_that_graph_exists(&store, &target)?;
let format = graph_content_negotiation(request)?; let format = rdf_content_negotiation(request)?;
let triples = store.quads_for_pattern(
let quads = store.quads_for_pattern(
None, None,
None, None,
None, None,
@ -927,14 +929,14 @@ fn handle_request(
ReadForWrite::build_response( ReadForWrite::build_response(
move |w| { move |w| {
Ok(( Ok((
GraphSerializer::from_format(format).triple_writer(w), RdfSerializer::from_format(format).serialize_to_write(w),
triples, quads,
)) ))
}, },
|(mut writer, mut triples)| { |(mut writer, mut quads)| {
Ok(if let Some(t) = triples.next() { Ok(if let Some(q) = quads.next() {
writer.write(&t?.into())?; writer.write_triple(&q?.into())?;
Some((writer, triples)) Some((writer, quads))
} else { } else {
writer.finish()?; writer.finish()?;
None None
@ -943,17 +945,22 @@ fn handle_request(
format.media_type(), format.media_type(),
) )
} else { } else {
let format = dataset_content_negotiation(request)?; let format = rdf_content_negotiation(request)?;
if !format.supports_datasets() {
return Err(bad_request(format!(
"It is not possible to serialize the full RDF dataset using {format} that does not support named graphs"
)));
}
ReadForWrite::build_response( ReadForWrite::build_response(
move |w| { move |w| {
Ok(( Ok((
DatasetSerializer::from_format(format).quad_writer(w), RdfSerializer::from_format(format).serialize_to_write(w),
store.iter(), store.iter(),
)) ))
}, },
|(mut writer, mut quads)| { |(mut writer, mut quads)| {
Ok(if let Some(q) = quads.next() { Ok(if let Some(q) = quads.next() {
writer.write(&q?)?; writer.write_quad(&q?)?;
Some((writer, quads)) Some((writer, quads))
} else { } else {
writer.finish()?; writer.finish()?;
@ -1227,17 +1234,17 @@ fn evaluate_sparql_query(
.with_body(body)) .with_body(body))
} }
QueryResults::Graph(triples) => { QueryResults::Graph(triples) => {
let format = graph_content_negotiation(request)?; let format = rdf_content_negotiation(request)?;
ReadForWrite::build_response( ReadForWrite::build_response(
move |w| { move |w| {
Ok(( Ok((
GraphSerializer::from_format(format).triple_writer(w), RdfSerializer::from_format(format).serialize_to_write(w),
triples, triples,
)) ))
}, },
|(mut writer, mut triples)| { |(mut writer, mut triples)| {
Ok(if let Some(t) = triples.next() { Ok(if let Some(t) = triples.next() {
writer.write(&t?)?; writer.write_triple(&t?)?;
Some((writer, triples)) Some((writer, triples))
} else { } else {
writer.finish()?; writer.finish()?;
@ -1403,26 +1410,26 @@ impl From<NamedGraphName> for GraphName {
} }
} }
fn graph_content_negotiation(request: &Request) -> Result<GraphFormat, HttpError> { fn rdf_content_negotiation(request: &Request) -> Result<RdfFormat, HttpError> {
content_negotiation(
request,
&[
GraphFormat::NTriples.media_type(),
GraphFormat::Turtle.media_type(),
GraphFormat::RdfXml.media_type(),
],
GraphFormat::from_media_type,
)
}
fn dataset_content_negotiation(request: &Request) -> Result<DatasetFormat, HttpError> {
content_negotiation( content_negotiation(
request, request,
&[ &[
DatasetFormat::NQuads.media_type(), "application/n-quads",
DatasetFormat::TriG.media_type(), "application/n-triples",
"application/rdf+xml",
"application/trig",
"application/turtle",
"application/xml",
"application/x-trig",
"application/x-turtle",
"text/n3",
"text/nquads",
"text/plain",
"text/turtle",
"text/xml",
"text/x-nquads",
], ],
DatasetFormat::from_media_type, RdfFormat::from_media_type,
) )
} }
@ -1430,10 +1437,15 @@ fn query_results_content_negotiation(request: &Request) -> Result<QueryResultsFo
content_negotiation( content_negotiation(
request, request,
&[ &[
QueryResultsFormat::Json.media_type(), "application/json",
QueryResultsFormat::Xml.media_type(), "application/sparql-results+json",
QueryResultsFormat::Csv.media_type(), "application/sparql-results+xml",
QueryResultsFormat::Tsv.media_type(), "application/xml",
"text/csv",
"text/json",
"text/tab-separated-values",
"text/tsv",
"text/xml",
], ],
QueryResultsFormat::from_media_type, QueryResultsFormat::from_media_type,
) )

Loading…
Cancel
Save