Adds a public API for transaction

Very similar to the one of the previous SledStore
pull/190/head
Tpt 3 years ago
parent 34a944036f
commit 045fb8cbf6
  1. 6
      lib/src/sparql/mod.rs
  2. 34
      lib/src/sparql/update.rs
  3. 638
      lib/src/store.rs

@ -23,7 +23,7 @@ use crate::sparql::plan_builder::PlanBuilder;
pub use crate::sparql::service::ServiceHandler; pub use crate::sparql::service::ServiceHandler;
use crate::sparql::service::{EmptyServiceHandler, ErrorConversionServiceHandler}; use crate::sparql::service::{EmptyServiceHandler, ErrorConversionServiceHandler};
pub(crate) use crate::sparql::update::evaluate_update; pub(crate) use crate::sparql::update::evaluate_update;
use crate::storage::Storage; use crate::storage::StorageReader;
pub use oxrdf::{Variable, VariableNameParseError}; pub use oxrdf::{Variable, VariableNameParseError};
pub use sparesults::QueryResultsFormat; pub use sparesults::QueryResultsFormat;
pub use spargebra::ParseError; pub use spargebra::ParseError;
@ -33,12 +33,12 @@ use std::time::Duration;
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
pub(crate) fn evaluate_query( pub(crate) fn evaluate_query(
storage: Storage, reader: StorageReader,
query: impl TryInto<Query, Error = impl Into<EvaluationError>>, query: impl TryInto<Query, Error = impl Into<EvaluationError>>,
options: QueryOptions, options: QueryOptions,
) -> Result<QueryResults, EvaluationError> { ) -> Result<QueryResults, EvaluationError> {
let query = query.try_into().map_err(std::convert::Into::into)?; let query = query.try_into().map_err(std::convert::Into::into)?;
let dataset = DatasetView::new(storage.snapshot(), &query.dataset); let dataset = DatasetView::new(reader, &query.dataset);
match query.inner { match query.inner {
spargebra::Query::Select { spargebra::Query::Select {
pattern, base_iri, .. pattern, base_iri, ..

@ -9,7 +9,7 @@ use crate::sparql::plan::EncodedTuple;
use crate::sparql::plan_builder::PlanBuilder; use crate::sparql::plan_builder::PlanBuilder;
use crate::sparql::{EvaluationError, Update, UpdateOptions}; use crate::sparql::{EvaluationError, Update, UpdateOptions};
use crate::storage::numeric_encoder::{Decoder, EncodedTerm}; use crate::storage::numeric_encoder::{Decoder, EncodedTerm};
use crate::storage::{Storage, StorageWriter}; use crate::storage::StorageWriter;
use oxiri::Iri; use oxiri::Iri;
use spargebra::algebra::{GraphPattern, GraphTarget}; use spargebra::algebra::{GraphPattern, GraphTarget};
use spargebra::term::{ use spargebra::term::{
@ -22,32 +22,28 @@ use std::collections::HashMap;
use std::io::BufReader; use std::io::BufReader;
use std::rc::Rc; use std::rc::Rc;
pub fn evaluate_update( pub fn evaluate_update<'a, 'b: 'a>(
storage: &Storage, transaction: &'a mut StorageWriter<'b>,
update: Update, update: &Update,
options: UpdateOptions, options: &UpdateOptions,
) -> Result<(), EvaluationError> { ) -> Result<(), EvaluationError> {
let base_iri = update.inner.base_iri.map(Rc::new); SimpleUpdateEvaluator {
storage.transaction(move |transaction| { transaction,
let client = Client::new(options.query_options.http_timeout); base_iri: update.inner.base_iri.clone().map(Rc::new),
SimpleUpdateEvaluator { options: options.clone(),
transaction, client: Client::new(options.query_options.http_timeout),
base_iri: base_iri.clone(), }
options: options.clone(), .eval_all(&update.inner.operations, &update.using_datasets)
client,
}
.eval_all(&update.inner.operations, &update.using_datasets)
})
} }
struct SimpleUpdateEvaluator<'a> { struct SimpleUpdateEvaluator<'a, 'b> {
transaction: StorageWriter<'a>, transaction: &'a mut StorageWriter<'b>,
base_iri: Option<Rc<Iri<String>>>, base_iri: Option<Rc<Iri<String>>>,
options: UpdateOptions, options: UpdateOptions,
client: Client, client: Client,
} }
impl SimpleUpdateEvaluator<'_> { impl<'a, 'b: 'a> SimpleUpdateEvaluator<'a, 'b> {
fn eval_all( fn eval_all(
&mut self, &mut self,
updates: &[GraphUpdateOperation], updates: &[GraphUpdateOperation],

@ -21,7 +21,7 @@
//! if let QueryResults::Solutions(mut solutions) = store.query("SELECT ?s WHERE { ?s ?p ?o }")? { //! if let QueryResults::Solutions(mut solutions) = store.query("SELECT ?s WHERE { ?s ?p ?o }")? {
//! assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into())); //! assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into()));
//! }; //! };
//! # 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::{
@ -35,8 +35,11 @@ use crate::sparql::{
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use crate::storage::bulk_load; use crate::storage::bulk_load;
use crate::storage::numeric_encoder::{Decoder, EncodedQuad, EncodedTerm}; use crate::storage::numeric_encoder::{Decoder, EncodedQuad, EncodedTerm};
use crate::storage::{ChainedDecodingQuadIterator, DecodingGraphIterator, Storage, StorageReader}; use crate::storage::{
ChainedDecodingQuadIterator, DecodingGraphIterator, Storage, StorageReader, StorageWriter,
};
pub use crate::storage::{CorruptionError, LoaderError, SerializerError, StorageError}; pub use crate::storage::{CorruptionError, LoaderError, SerializerError, StorageError};
use std::error::Error;
use std::io::{BufRead, Write}; use std::io::{BufRead, Write};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use std::path::Path; use std::path::Path;
@ -76,15 +79,13 @@ use std::{fmt, str};
/// # /// #
/// # }; /// # };
/// # remove_dir_all("example.db")?; /// # remove_dir_all("example.db")?;
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
#[derive(Clone)] #[derive(Clone)]
pub struct Store { pub struct Store {
storage: Storage, storage: Storage,
} }
//TODO: indexes for the default graph and indexes for the named graphs (no more Optional and space saving)
impl Store { impl Store {
/// Creates a temporary [`Store`] that will be deleted after drop. /// Creates a temporary [`Store`] that will be deleted after drop.
pub fn new() -> Result<Self, StorageError> { pub fn new() -> Result<Self, StorageError> {
@ -119,7 +120,7 @@ impl Store {
/// if let QueryResults::Solutions(mut solutions) = store.query("SELECT ?s WHERE { ?s ?p ?o }")? { /// if let QueryResults::Solutions(mut solutions) = store.query("SELECT ?s WHERE { ?s ?p ?o }")? {
/// assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into_owned().into())); /// assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into_owned().into()));
/// } /// }
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
pub fn query( pub fn query(
&self, &self,
@ -130,7 +131,6 @@ impl Store {
/// Executes a [SPARQL 1.1 query](https://www.w3.org/TR/sparql11-query/) with some options. /// Executes a [SPARQL 1.1 query](https://www.w3.org/TR/sparql11-query/) with some options.
/// ///
///
/// Usage example with a custom function serializing terms to N-Triples: /// Usage example with a custom function serializing terms to N-Triples:
/// ``` /// ```
/// use oxigraph::store::Store; /// use oxigraph::store::Store;
@ -147,14 +147,14 @@ impl Store {
/// )? { /// )? {
/// assert_eq!(solutions.next().unwrap()?.get("nt"), Some(&Literal::from("\"1\"^^<http://www.w3.org/2001/XMLSchema#integer>").into())); /// assert_eq!(solutions.next().unwrap()?.get("nt"), Some(&Literal::from("\"1\"^^<http://www.w3.org/2001/XMLSchema#integer>").into()));
/// } /// }
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
pub fn query_opt( pub fn query_opt(
&self, &self,
query: impl TryInto<Query, Error = impl Into<EvaluationError>>, query: impl TryInto<Query, Error = impl Into<EvaluationError>>,
options: QueryOptions, options: QueryOptions,
) -> Result<QueryResults, EvaluationError> { ) -> Result<QueryResults, EvaluationError> {
evaluate_query(self.storage.clone(), query, options) evaluate_query(self.storage.snapshot(), query, options)
} }
/// Retrieves quads with a filter on each quad component /// Retrieves quads with a filter on each quad component
@ -174,7 +174,7 @@ impl Store {
/// // quad filter by object /// // quad filter by object
/// let results = store.quads_for_pattern(None, None, Some((&ex).into()), None).collect::<Result<Vec<_>,_>>()?; /// let results = store.quads_for_pattern(None, None, Some((&ex).into()), None).collect::<Result<Vec<_>,_>>()?;
/// assert_eq!(vec![quad], results); /// assert_eq!(vec![quad], results);
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
pub fn quads_for_pattern( pub fn quads_for_pattern(
&self, &self,
@ -196,11 +196,45 @@ impl Store {
} }
/// Returns all the quads contained in the store. /// Returns all the quads contained in the store.
///
/// Usage example:
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::model::*;
///
/// let store = Store::new()?;
///
/// // insertion
/// let ex = NamedNode::new("http://example.com")?;
/// let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), GraphName::DefaultGraph);
/// store.insert(&quad)?;
///
/// // quad filter by object
/// let results = store.iter().collect::<Result<Vec<_>,_>>()?;
/// assert_eq!(vec![quad], results);
/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ```
pub fn iter(&self) -> QuadIter { pub fn iter(&self) -> QuadIter {
self.quads_for_pattern(None, None, None, None) self.quads_for_pattern(None, None, None, None)
} }
/// Checks if this store contains a given quad. /// Checks if this store contains a given quad.
///
/// Usage example:
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::model::*;
///
/// let ex = NamedNodeRef::new("http://example.com")?;
/// let quad = QuadRef::new(ex, ex, ex, ex);
///
/// let store = Store::new()?;
/// assert!(!store.contains(quad)?);
///
/// store.insert(quad)?;
/// assert!(store.contains(quad)?);
/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ```
pub fn contains<'a>(&self, quad: impl Into<QuadRef<'a>>) -> Result<bool, StorageError> { pub fn contains<'a>(&self, quad: impl Into<QuadRef<'a>>) -> Result<bool, StorageError> {
let quad = EncodedQuad::from(quad.into()); let quad = EncodedQuad::from(quad.into());
self.storage.snapshot().contains(&quad) self.storage.snapshot().contains(&quad)
@ -209,15 +243,74 @@ impl Store {
/// Returns the number of quads in the store. /// Returns the number of quads in the store.
/// ///
/// Warning: this function executes a full scan. /// Warning: this function executes a full scan.
///
/// Usage example:
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::model::*;
///
/// let ex = NamedNodeRef::new("http://example.com")?;
/// let store = Store::new()?;
/// store.insert(QuadRef::new(ex, ex, ex, ex))?;
/// store.insert(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?;
/// assert_eq!(2, store.len()?);
/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ```
pub fn len(&self) -> Result<usize, StorageError> { pub fn len(&self) -> Result<usize, StorageError> {
self.storage.snapshot().len() self.storage.snapshot().len()
} }
/// Returns if the store is empty. /// Returns if the store is empty.
///
/// Usage example:
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::model::*;
///
/// let store = Store::new()?;
/// assert!(store.is_empty()?);
///
/// let ex = NamedNodeRef::new("http://example.com")?;
/// store.insert(QuadRef::new(ex, ex, ex, ex))?;
/// assert!(!store.is_empty()?);
/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ```
pub fn is_empty(&self) -> Result<bool, StorageError> { pub fn is_empty(&self) -> Result<bool, StorageError> {
self.storage.snapshot().is_empty() self.storage.snapshot().is_empty()
} }
/// Executes a transaction.
///
/// Transaction ensures the "repeatable read" isolation level: the store only exposes changes that have
/// been "committed" (i.e. no partial writes) and the exposed state does not change for the complete duration
/// of a read operation (e.g. a SPARQL query) or a read/write operation (e.g. a SPARQL update).
///
/// Usage example:
/// ```
/// use oxigraph::store::{StorageError, Store};
/// use oxigraph::model::*;
///
/// let store = Store::new()?;
/// let a = NamedNodeRef::new("http://example.com/a")?;
/// let b = NamedNodeRef::new("http://example.com/b")?;
///
/// // Copy all triples about ex:a to triples about ex:b
/// store.transaction(|mut transaction| {
/// for q in transaction.quads_for_pattern(Some(a.into()), None, None, None) {
/// let q = q?;
/// transaction.insert(QuadRef::new(b, &q.predicate, &q.object, &q.graph_name))?;
/// }
/// Result::<_, StorageError>::Ok(())
/// })?;
/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ```
pub fn transaction<'a, 'b: 'a, T, E: Error + 'static + From<StorageError>>(
&'b self,
f: impl Fn(Transaction<'a>) -> Result<T, E>,
) -> Result<T, E> {
self.storage.transaction(|writer| f(Transaction { writer }))
}
/// Executes a [SPARQL 1.1 update](https://www.w3.org/TR/sparql11-update/). /// Executes a [SPARQL 1.1 update](https://www.w3.org/TR/sparql11-update/).
/// ///
/// The store does not track the existence of empty named graphs. /// The store does not track the existence of empty named graphs.
@ -234,9 +327,9 @@ impl Store {
/// store.update("INSERT DATA { <http://example.com> <http://example.com> <http://example.com> }")?; /// store.update("INSERT DATA { <http://example.com> <http://example.com> <http://example.com> }")?;
/// ///
/// // we inspect the store contents /// // we inspect the store contents
/// let ex = NamedNodeRef::new("http://example.com").unwrap(); /// let ex = NamedNodeRef::new("http://example.com")?;
/// assert!(store.contains(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?); /// assert!(store.contains(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?);
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
pub fn update( pub fn update(
&self, &self,
@ -260,23 +353,22 @@ impl Store {
/// |args| args.get(0).map(|t| Literal::from(t.to_string()).into()) /// |args| args.get(0).map(|t| Literal::from(t.to_string()).into())
/// ) /// )
/// )?; /// )?;
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
pub fn update_opt( pub fn update_opt(
&self, &self,
update: impl TryInto<Update, Error = impl Into<EvaluationError>>, update: impl TryInto<Update, Error = impl Into<EvaluationError>>,
options: impl Into<UpdateOptions>, options: impl Into<UpdateOptions>,
) -> Result<(), EvaluationError> { ) -> Result<(), EvaluationError> {
evaluate_update( let update = update.try_into().map_err(std::convert::Into::into)?;
&self.storage, let options = options.into();
update.try_into().map_err(std::convert::Into::into)?, self.storage
options.into(), .transaction(|mut t| evaluate_update(&mut t, &update, &options))
)
} }
/// Loads a graph file (i.e. triples) into the store. /// Loads a graph file (i.e. triples) into the store.
/// ///
/// This function is atomic and quite slow and memory hungry. To get much better performances you might want to use [`bulk_load_graph`](Store::bulk_load_graph). /// This function is atomic, quite slow and memory hungry. To get much better performances you might want to use [`bulk_load_graph`](Store::bulk_load_graph).
/// ///
/// Usage example: /// Usage example:
/// ``` /// ```
@ -293,12 +385,8 @@ impl Store {
/// // we inspect the store contents /// // we inspect the store contents
/// let ex = NamedNodeRef::new("http://example.com")?; /// let ex = NamedNodeRef::new("http://example.com")?;
/// assert!(store.contains(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?); /// assert!(store.contains(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?);
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
///
/// Errors related to parameter validation like the base IRI use the [`InvalidInput`](std::io::ErrorKind::InvalidInput) error kind.
/// Errors related to a bad syntax in the loaded file use the [`InvalidData`](std::io::ErrorKind::InvalidData) or [`UnexpectedEof`](std::io::ErrorKind::UnexpectedEof) error kinds.
/// Errors related to data loading into the store use the other error kinds.
pub fn load_graph<'a>( pub fn load_graph<'a>(
&self, &self,
reader: impl BufRead, reader: impl BufRead,
@ -326,7 +414,7 @@ impl Store {
/// Loads a dataset file (i.e. quads) into the store. /// Loads a dataset file (i.e. quads) into the store.
/// ///
/// This function is atomic and quite slow. To get much better performances you might want to [`bulk_load_dataset`](Store::bulk_load_dataset). /// This function is atomic, quite slow and memory hungry. To get much better performances you might want to [`bulk_load_dataset`](Store::bulk_load_dataset).
/// ///
/// Usage example: /// Usage example:
/// ``` /// ```
@ -343,12 +431,8 @@ impl Store {
/// // we inspect the store contents /// // we inspect the store contents
/// let ex = NamedNodeRef::new("http://example.com")?; /// let ex = NamedNodeRef::new("http://example.com")?;
/// assert!(store.contains(QuadRef::new(ex, ex, ex, ex))?); /// assert!(store.contains(QuadRef::new(ex, ex, ex, ex))?);
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
///
/// Errors related to parameter validation like the base IRI use the [`InvalidInput`](std::io::ErrorKind::InvalidInput) error kind.
/// Errors related to a bad syntax in the loaded file use the [`InvalidData`](std::io::ErrorKind::InvalidData) or [`UnexpectedEof`](std::io::ErrorKind::UnexpectedEof) error kinds.
/// Errors related to data loading into the store use the other error kinds.
pub fn load_dataset( pub fn load_dataset(
&self, &self,
reader: impl BufRead, reader: impl BufRead,
@ -386,24 +470,22 @@ impl Store {
/// store.insert(quad)?; /// store.insert(quad)?;
/// ///
/// assert!(store.contains(quad)?); /// assert!(store.contains(quad)?);
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
pub fn insert<'a>(&self, quad: impl Into<QuadRef<'a>>) -> Result<bool, StorageError> { pub fn insert<'a>(&self, quad: impl Into<QuadRef<'a>>) -> Result<bool, StorageError> {
let quad = quad.into(); let quad = quad.into();
self.storage.transaction(move |mut t| t.insert(quad)) self.transaction(|mut t| t.insert(quad))
} }
/// Adds atomically a set of quads to this store. /// Adds atomically a set of quads to this store.
/// ///
/// Warning: This operation uses a memory heavy transaction internally, use [`bulk_extend`](Store::bulk_extend) if you plan to add ten of millions of triples. /// Warning: This operation uses a memory heavy transaction internally, use [`bulk_extend`](Store::bulk_extend) if you plan to add ten of millions of triples.
pub fn extend(&self, quads: impl IntoIterator<Item = Quad>) -> Result<(), StorageError> { pub fn extend(
let quads = quads.into_iter().collect::<Vec<_>>(); &self,
self.storage.transaction(move |mut t| { quads: impl IntoIterator<Item = impl Into<Quad>>,
for quad in &quads { ) -> Result<(), StorageError> {
t.insert(quad.into())?; let quads = quads.into_iter().map(|q| q.into()).collect::<Vec<_>>();
} self.transaction(move |mut t| t.extend(&quads))
Ok(())
})
} }
/// Removes a quad from this store. /// Removes a quad from this store.
@ -423,11 +505,11 @@ impl Store {
/// store.remove(quad)?; /// store.remove(quad)?;
/// ///
/// assert!(!store.contains(quad)?); /// assert!(!store.contains(quad)?);
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
pub fn remove<'a>(&self, quad: impl Into<QuadRef<'a>>) -> Result<bool, StorageError> { pub fn remove<'a>(&self, quad: impl Into<QuadRef<'a>>) -> Result<bool, StorageError> {
let quad = quad.into(); let quad = quad.into();
self.storage.transaction(move |mut t| t.remove(quad)) self.transaction(move |mut t| t.remove(quad))
} }
/// Dumps a store graph into a file. /// Dumps a store graph into a file.
@ -491,7 +573,7 @@ impl Store {
Ok(()) Ok(())
} }
/// Returns all the store named graphs /// Returns all the store named graphs.
/// ///
/// Usage example: /// Usage example:
/// ``` /// ```
@ -503,7 +585,7 @@ impl Store {
/// store.insert(QuadRef::new(&ex, &ex, &ex, &ex))?; /// store.insert(QuadRef::new(&ex, &ex, &ex, &ex))?;
/// store.insert(QuadRef::new(&ex, &ex, &ex, GraphNameRef::DefaultGraph))?; /// store.insert(QuadRef::new(&ex, &ex, &ex, GraphNameRef::DefaultGraph))?;
/// assert_eq!(vec![NamedOrBlankNode::from(ex)], store.named_graphs().collect::<Result<Vec<_>,_>>()?); /// assert_eq!(vec![NamedOrBlankNode::from(ex)], store.named_graphs().collect::<Result<Vec<_>,_>>()?);
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
pub fn named_graphs(&self) -> GraphNameIter { pub fn named_graphs(&self) -> GraphNameIter {
let reader = self.storage.snapshot(); let reader = self.storage.snapshot();
@ -524,7 +606,7 @@ impl Store {
/// let store = Store::new()?; /// let store = Store::new()?;
/// store.insert(QuadRef::new(&ex, &ex, &ex, &ex))?; /// store.insert(QuadRef::new(&ex, &ex, &ex, &ex))?;
/// assert!(store.contains_named_graph(&ex)?); /// assert!(store.contains_named_graph(&ex)?);
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
pub fn contains_named_graph<'a>( pub fn contains_named_graph<'a>(
&self, &self,
@ -548,15 +630,14 @@ impl Store {
/// store.insert_named_graph(ex)?; /// store.insert_named_graph(ex)?;
/// ///
/// assert_eq!(store.named_graphs().collect::<Result<Vec<_>,_>>()?, vec![ex.into_owned().into()]); /// assert_eq!(store.named_graphs().collect::<Result<Vec<_>,_>>()?, vec![ex.into_owned().into()]);
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
pub fn insert_named_graph<'a>( pub fn insert_named_graph<'a>(
&self, &self,
graph_name: impl Into<NamedOrBlankNodeRef<'a>>, graph_name: impl Into<NamedOrBlankNodeRef<'a>>,
) -> Result<bool, StorageError> { ) -> Result<bool, StorageError> {
let graph_name = graph_name.into(); let graph_name = graph_name.into();
self.storage self.transaction(|mut t| t.insert_named_graph(graph_name))
.transaction(move |mut t| t.insert_named_graph(graph_name))
} }
/// Clears a graph from this store. /// Clears a graph from this store.
@ -575,15 +656,14 @@ impl Store {
/// store.clear_graph(ex)?; /// store.clear_graph(ex)?;
/// assert!(store.is_empty()?); /// assert!(store.is_empty()?);
/// assert_eq!(1, store.named_graphs().count()); /// assert_eq!(1, store.named_graphs().count());
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
pub fn clear_graph<'a>( pub fn clear_graph<'a>(
&self, &self,
graph_name: impl Into<GraphNameRef<'a>>, graph_name: impl Into<GraphNameRef<'a>>,
) -> Result<(), StorageError> { ) -> Result<(), StorageError> {
let graph_name = graph_name.into(); let graph_name = graph_name.into();
self.storage self.transaction(|mut t| t.clear_graph(graph_name))
.transaction(move |mut t| t.clear_graph(graph_name))
} }
/// Removes a graph from this store. /// Removes a graph from this store.
@ -604,15 +684,14 @@ impl Store {
/// store.remove_named_graph(ex)?; /// store.remove_named_graph(ex)?;
/// assert!(store.is_empty()?); /// assert!(store.is_empty()?);
/// assert_eq!(0, store.named_graphs().count()); /// assert_eq!(0, store.named_graphs().count());
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
pub fn remove_named_graph<'a>( pub fn remove_named_graph<'a>(
&self, &self,
graph_name: impl Into<NamedOrBlankNodeRef<'a>>, graph_name: impl Into<NamedOrBlankNodeRef<'a>>,
) -> Result<bool, StorageError> { ) -> Result<bool, StorageError> {
let graph_name = graph_name.into(); let graph_name = graph_name.into();
self.storage self.transaction(|mut t| t.remove_named_graph(graph_name))
.transaction(move |mut t| t.remove_named_graph(graph_name))
} }
/// Clears the store. /// Clears the store.
@ -630,10 +709,10 @@ impl Store {
/// ///
/// store.clear()?; /// store.clear()?;
/// assert!(store.is_empty()?); /// assert!(store.is_empty()?);
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
pub fn clear(&self) -> Result<(), StorageError> { pub fn clear(&self) -> Result<(), StorageError> {
self.storage.transaction(|mut t| t.clear()) self.transaction(|mut t| t.clear())
} }
/// Flushes all buffers and ensures that all writes are saved on disk. /// Flushes all buffers and ensures that all writes are saved on disk.
@ -679,12 +758,8 @@ impl Store {
/// // we inspect the store contents /// // we inspect the store contents
/// let ex = NamedNodeRef::new("http://example.com")?; /// let ex = NamedNodeRef::new("http://example.com")?;
/// assert!(store.contains(QuadRef::new(ex, ex, ex, ex))?); /// assert!(store.contains(QuadRef::new(ex, ex, ex, ex))?);
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
///
/// Errors related to parameter validation like the base IRI use the [`InvalidInput`](std::io::ErrorKind::InvalidInput) error kind.
/// Errors related to a bad syntax in the loaded file use the [`InvalidData`](std::io::ErrorKind::InvalidData) or [`UnexpectedEof`](std::io::ErrorKind::UnexpectedEof) error kinds.
/// Errors related to data loading into the store use the other error kinds.
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub fn bulk_load_dataset( pub fn bulk_load_dataset(
&self, &self,
@ -726,12 +801,8 @@ impl Store {
/// // we inspect the store contents /// // we inspect the store contents
/// let ex = NamedNodeRef::new("http://example.com")?; /// let ex = NamedNodeRef::new("http://example.com")?;
/// assert!(store.contains(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?); /// assert!(store.contains(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?);
/// # Result::<_,Box<dyn std::error::Error>>::Ok(()) /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ``` /// ```
///
/// Errors related to parameter validation like the base IRI use the [`InvalidInput`](std::io::ErrorKind::InvalidInput) error kind.
/// Errors related to a bad syntax in the loaded file use the [`InvalidData`](std::io::ErrorKind::InvalidData) or [`UnexpectedEof`](std::io::ErrorKind::UnexpectedEof) error kinds.
/// Errors related to data loading into the store use the other error kinds.
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub fn bulk_load_graph<'a>( pub fn bulk_load_graph<'a>(
&self, &self,
@ -777,6 +848,441 @@ impl fmt::Display for Store {
} }
} }
/// An object to do operations during a transaction.
///
/// See [`Store::transaction`] for a more detailed description.
pub struct Transaction<'a> {
writer: StorageWriter<'a>,
}
impl<'a> Transaction<'a> {
/// Executes a [SPARQL 1.1 query](https://www.w3.org/TR/sparql11-query/).
///
/// Usage example:
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::model::*;
/// use oxigraph::sparql::{EvaluationError, QueryResults};
///
/// let store = Store::new()?;
/// store.transaction(|mut transaction| {
/// if let QueryResults::Solutions(solutions) = transaction.query("SELECT ?s WHERE { ?s ?p ?o }")? {
/// for solution in solutions {
/// if let Some(Term::NamedNode(s)) = solution?.get("s") {
/// transaction.insert(QuadRef::new(s, vocab::rdf::TYPE, NamedNodeRef::new_unchecked("http://example.com"), GraphNameRef::DefaultGraph))?;
/// }
/// }
/// }
/// Result::<_, EvaluationError>::Ok(())
/// })?;
/// # Result::<_, EvaluationError>::Ok(())
/// ```
pub fn query(
&self,
query: impl TryInto<Query, Error = impl Into<EvaluationError>>,
) -> Result<QueryResults, EvaluationError> {
self.query_opt(query, QueryOptions::default())
}
/// Executes a [SPARQL 1.1 query](https://www.w3.org/TR/sparql11-query/) with some options.
///
/// Usage example with a custom function serializing terms to N-Triples:
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::model::*;
/// use oxigraph::sparql::{EvaluationError, QueryOptions, QueryResults};
///
/// let store = Store::new()?;
/// store.transaction(|mut transaction| {
/// if let QueryResults::Solutions(solutions) = transaction.query_opt(
/// "SELECT ?s (<http://www.w3.org/ns/formats/N-Triples>(?s) AS ?nt) WHERE { ?s ?p ?o }",
/// QueryOptions::default().with_custom_function(
/// NamedNode::new_unchecked("http://www.w3.org/ns/formats/N-Triples"),
/// |args| args.get(0).map(|t| Literal::from(t.to_string()).into())
/// )
/// )? {
/// for solution in solutions {
/// let solution = solution?;
/// if let (Some(Term::NamedNode(s)), Some(nt)) = (solution.get("s"), solution.get("nt")) {
/// transaction.insert(QuadRef::new(s, NamedNodeRef::new_unchecked("http://example.com/n-triples-representation"), nt, GraphNameRef::DefaultGraph))?;
/// }
/// }
/// }
/// Result::<_, EvaluationError>::Ok(())
/// })?;
/// # Result::<_, EvaluationError>::Ok(())
/// ```
pub fn query_opt(
&self,
query: impl TryInto<Query, Error = impl Into<EvaluationError>>,
options: QueryOptions,
) -> Result<QueryResults, EvaluationError> {
evaluate_query(self.writer.reader(), query, options)
}
/// Retrieves quads with a filter on each quad component.
///
/// Usage example:
/// Usage example:
/// ```
/// use oxigraph::store::{StorageError, Store};
/// use oxigraph::model::*;
///
/// let store = Store::new()?;
/// let a = NamedNodeRef::new("http://example.com/a")?;
/// let b = NamedNodeRef::new("http://example.com/b")?;
///
/// // Copy all triples about ex:a to triples about ex:b
/// store.transaction(|mut transaction| {
/// for q in transaction.quads_for_pattern(Some(a.into()), None, None, None) {
/// let q = q?;
/// transaction.insert(QuadRef::new(b, &q.predicate, &q.object, &q.graph_name))?;
/// }
/// Result::<_, StorageError>::Ok(())
/// })?;
/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
/// ```
pub fn quads_for_pattern(
&self,
subject: Option<SubjectRef<'_>>,
predicate: Option<NamedNodeRef<'_>>,
object: Option<TermRef<'_>>,
graph_name: Option<GraphNameRef<'_>>,
) -> QuadIter {
let reader = self.writer.reader();
QuadIter {
iter: reader.quads_for_pattern(
subject.map(EncodedTerm::from).as_ref(),
predicate.map(EncodedTerm::from).as_ref(),
object.map(EncodedTerm::from).as_ref(),
graph_name.map(EncodedTerm::from).as_ref(),
),
reader,
}
}
/// Returns all the quads contained in the store.
pub fn iter(&self) -> QuadIter {
self.quads_for_pattern(None, None, None, None)
}
/// Checks if this store contains a given quad.
pub fn contains<'b>(&self, quad: impl Into<QuadRef<'b>>) -> Result<bool, StorageError> {
let quad = EncodedQuad::from(quad.into());
self.writer.reader().contains(&quad)
}
/// Returns the number of quads in the store.
///
/// Warning: this function executes a full scan.
pub fn len(&self) -> Result<usize, StorageError> {
self.writer.reader().len()
}
/// Returns if the store is empty.
pub fn is_empty(&self) -> Result<bool, StorageError> {
self.writer.reader().is_empty()
}
/// Executes a [SPARQL 1.1 update](https://www.w3.org/TR/sparql11-update/).
///
/// The store does not track the existence of empty named graphs.
/// This method has no ACID guarantees.
///
/// Usage example:
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::model::*;
/// use oxigraph::sparql::EvaluationError;
///
/// let store = Store::new()?;
/// store.transaction(|mut transaction| {
/// // insertion
/// transaction.update("INSERT DATA { <http://example.com> <http://example.com> <http://example.com> }")?;
///
/// // we inspect the store contents
/// let ex = NamedNodeRef::new_unchecked("http://example.com");
/// assert!(transaction.contains(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?);
/// Result::<_, EvaluationError>::Ok(())
/// })?;
/// # Result::<_, EvaluationError>::Ok(())
/// ```
pub fn update(
&mut self,
update: impl TryInto<Update, Error = impl Into<EvaluationError>>,
) -> Result<(), EvaluationError> {
self.update_opt(update, UpdateOptions::default())
}
/// Executes a [SPARQL 1.1 update](https://www.w3.org/TR/sparql11-update/) with some options.
pub fn update_opt(
&mut self,
update: impl TryInto<Update, Error = impl Into<EvaluationError>>,
options: impl Into<UpdateOptions>,
) -> Result<(), EvaluationError> {
evaluate_update(
&mut self.writer,
&update.try_into().map_err(std::convert::Into::into)?,
&options.into(),
)
}
/// Loads a graph file (i.e. triples) into the store.
///
/// Usage example:
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::io::GraphFormat;
/// use oxigraph::model::*;
///
/// let store = Store::new()?;
///
/// // insertion
/// let file = b"<http://example.com> <http://example.com> <http://example.com> .";
/// store.transaction(|mut transaction| {
/// transaction.load_graph(file.as_ref(), GraphFormat::NTriples, &GraphName::DefaultGraph, None)
/// })?;
///
/// // we inspect the store contents
/// let ex = NamedNodeRef::new_unchecked("http://example.com");
/// assert!(store.contains(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?);
/// # Result::<_,oxigraph::store::LoaderError>::Ok(())
/// ```
pub fn load_graph<'b>(
&mut self,
reader: impl BufRead,
format: GraphFormat,
to_graph_name: impl Into<GraphNameRef<'b>>,
base_iri: Option<&str>,
) -> Result<(), LoaderError> {
let mut parser = GraphParser::from_format(format);
if let Some(base_iri) = base_iri {
parser = parser
.with_base_iri(base_iri)
.map_err(|e| ParseError::invalid_base_iri(base_iri, e))?;
}
let to_graph_name = to_graph_name.into();
for triple in parser.read_triples(reader)? {
self.writer
.insert(triple?.as_ref().in_graph(to_graph_name))?;
}
Ok(())
}
/// Loads a dataset file (i.e. quads) into the store.
///
/// Usage example:
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::io::DatasetFormat;
/// use oxigraph::model::*;
///
/// let store = Store::new()?;
///
/// // insertion
/// let file = b"<http://example.com> <http://example.com> <http://example.com> <http://example.com> .";
/// store.transaction(|mut transaction| {
/// transaction.load_dataset(file.as_ref(), DatasetFormat::NQuads, None)
/// })?;
///
/// // we inspect the store contents
/// let ex = NamedNodeRef::new_unchecked("http://example.com");
/// assert!(store.contains(QuadRef::new(ex, ex, ex, ex))?);
/// # Result::<_,oxigraph::store::LoaderError>::Ok(())
/// ```
pub fn load_dataset(
&mut self,
reader: impl BufRead,
format: DatasetFormat,
base_iri: Option<&str>,
) -> Result<(), LoaderError> {
let mut parser = DatasetParser::from_format(format);
if let Some(base_iri) = base_iri {
parser = parser
.with_base_iri(base_iri)
.map_err(|e| ParseError::invalid_base_iri(base_iri, e))?;
}
for quad in parser.read_quads(reader)? {
self.writer.insert(quad?.as_ref())?;
}
Ok(())
}
/// Adds a quad to this store.
///
/// Returns `true` if the quad was not already in the store.
///
/// Usage example:
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::model::*;
///
/// let ex = NamedNodeRef::new_unchecked("http://example.com");
/// let quad = QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph);
///
/// let store = Store::new()?;
/// store.transaction(|mut transaction| {
/// transaction.insert(quad)
/// })?;
/// assert!(store.contains(quad)?);
/// # Result::<_,oxigraph::store::StorageError>::Ok(())
/// ```
pub fn insert<'b>(&mut self, quad: impl Into<QuadRef<'b>>) -> Result<bool, StorageError> {
self.writer.insert(quad.into())
}
/// Adds atomically a set of quads to this store.
///
/// Warning: This operation uses a memory heavy transaction internally, use [`bulk_extend`](Store::bulk_extend) if you plan to add ten of millions of triples.
pub fn extend<'b>(
&mut self,
quads: impl IntoIterator<Item = impl Into<QuadRef<'b>>>,
) -> Result<(), StorageError> {
for quad in quads {
self.writer.insert(quad.into())?;
}
Ok(())
}
/// Removes a quad from this store.
///
/// Returns `true` if the quad was in the store and has been removed.
///
/// Usage example:
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::model::*;
///
/// let ex = NamedNodeRef::new_unchecked("http://example.com");
/// let quad = QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph);
/// let store = Store::new()?;
/// store.transaction(|mut transaction| {
/// transaction.insert(quad)?;
/// transaction.remove(quad)
/// })?;
/// assert!(!store.contains(quad)?);
/// # Result::<_,oxigraph::store::StorageError>::Ok(())
/// ```
pub fn remove<'b>(&mut self, quad: impl Into<QuadRef<'b>>) -> Result<bool, StorageError> {
self.writer.remove(quad.into())
}
/// Returns all the store named graphs.
pub fn named_graphs(&self) -> GraphNameIter {
let reader = self.writer.reader();
GraphNameIter {
iter: reader.named_graphs(),
reader,
}
}
/// Checks if the store contains a given graph.
pub fn contains_named_graph<'b>(
&self,
graph_name: impl Into<NamedOrBlankNodeRef<'b>>,
) -> Result<bool, StorageError> {
self.writer
.reader()
.contains_named_graph(&EncodedTerm::from(graph_name.into()))
}
/// Inserts a graph into this store.
///
/// Returns `true` if the graph was not already in the store.
///
/// Usage example:
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::model::NamedNodeRef;
///
/// let ex = NamedNodeRef::new_unchecked("http://example.com");
/// let store = Store::new()?;
/// store.transaction(|mut transaction| {
/// transaction.insert_named_graph(ex)
/// })?;
/// assert_eq!(store.named_graphs().collect::<Result<Vec<_>,_>>()?, vec![ex.into_owned().into()]);
/// # Result::<_,oxigraph::store::StorageError>::Ok(())
/// ```
pub fn insert_named_graph<'b>(
&mut self,
graph_name: impl Into<NamedOrBlankNodeRef<'b>>,
) -> Result<bool, StorageError> {
self.writer.insert_named_graph(graph_name.into())
}
/// Clears a graph from this store.
///
/// Usage example:
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::model::{NamedNodeRef, QuadRef};
///
/// let ex = NamedNodeRef::new_unchecked("http://example.com");
/// let quad = QuadRef::new(ex, ex, ex, ex);
/// let store = Store::new()?;
/// store.transaction(|mut transaction| {
/// transaction.insert(quad)?;
/// transaction.clear_graph(ex)
/// })?;
/// assert!(store.is_empty()?);
/// assert_eq!(1, store.named_graphs().count());
/// # Result::<_,oxigraph::store::StorageError>::Ok(())
/// ```
pub fn clear_graph<'b>(
&mut self,
graph_name: impl Into<GraphNameRef<'b>>,
) -> Result<(), StorageError> {
self.writer.clear_graph(graph_name.into())
}
/// Removes a graph from this store.
///
/// Returns `true` if the graph was in the store and has been removed.
///
/// Usage example:
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::model::{NamedNodeRef, QuadRef};
///
/// let ex = NamedNodeRef::new_unchecked("http://example.com");
/// let quad = QuadRef::new(ex, ex, ex, ex);
/// let store = Store::new()?;
/// store.transaction(|mut transaction| {
/// transaction.insert(quad)?;
/// transaction.remove_named_graph(ex)
/// })?;
/// assert!(store.is_empty()?);
/// assert_eq!(0, store.named_graphs().count());
/// # Result::<_,oxigraph::store::StorageError>::Ok(())
/// ```
pub fn remove_named_graph<'b>(
&mut self,
graph_name: impl Into<NamedOrBlankNodeRef<'b>>,
) -> Result<bool, StorageError> {
self.writer.remove_named_graph(graph_name.into())
}
/// Clears the store.
///
/// Usage example:
/// ```
/// use oxigraph::store::Store;
/// use oxigraph::model::*;
///
/// let ex = NamedNodeRef::new_unchecked("http://example.com");
/// let store = Store::new()?;
/// store.transaction(|mut transaction| {
/// transaction.insert(QuadRef::new(ex, ex, ex, ex))?;
/// transaction.clear()
/// })?;
/// assert!(store.is_empty()?);
/// # Result::<_,oxigraph::store::StorageError>::Ok(())
/// ```
pub fn clear(&mut self) -> Result<(), StorageError> {
self.writer.clear()
}
}
/// An iterator returning the quads contained in a [`Store`]. /// An iterator returning the quads contained in a [`Store`].
pub struct QuadIter { pub struct QuadIter {
iter: ChainedDecodingQuadIterator, iter: ChainedDecodingQuadIterator,

Loading…
Cancel
Save