Fork of https://github.com/oxigraph/oxigraph.git for the purpose of NextGraph project
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1115 lines
34 KiB
1115 lines
34 KiB
//! Store based on the [Sled](https://sled.rs/) key-value database.
|
|
|
|
use crate::error::invalid_data_error;
|
|
use crate::io::{DatasetFormat, GraphFormat};
|
|
use crate::model::*;
|
|
use crate::sparql::{EvaluationError, Query, QueryOptions, QueryResult, SimplePreparedQuery};
|
|
use crate::store::numeric_encoder::*;
|
|
use crate::store::{
|
|
dump_dataset, dump_graph, load_dataset, load_graph, ReadableEncodedStore, StoreOrParseError,
|
|
WritableEncodedStore,
|
|
};
|
|
use sled::transaction::{
|
|
ConflictableTransactionError, TransactionError, Transactional, TransactionalTree,
|
|
UnabortableTransactionError,
|
|
};
|
|
use sled::{Config, Iter, Tree};
|
|
use std::convert::TryInto;
|
|
use std::error::Error;
|
|
use std::io::{BufRead, Cursor, Write};
|
|
use std::path::Path;
|
|
use std::{fmt, io, str};
|
|
|
|
/// Store based on the [Sled](https://sled.rs/) key-value database.
|
|
/// It encodes a [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset) and allows to query and update it using SPARQL.
|
|
///
|
|
/// To use it, the `"sled"` feature needs to be activated.
|
|
///
|
|
/// Warning: quad insertions and deletions are not (yet) atomic.
|
|
///
|
|
/// Usage example:
|
|
/// ```
|
|
/// use oxigraph::SledStore;
|
|
/// use oxigraph::sparql::{QueryOptions, QueryResult};
|
|
/// use oxigraph::model::*;
|
|
/// # use std::fs::remove_dir_all;
|
|
///
|
|
/// # {
|
|
/// let store = SledStore::open("example.db")?;
|
|
///
|
|
/// // insertion
|
|
/// let ex = NamedNode::new("http://example.com")?;
|
|
/// let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), None);
|
|
/// store.insert(&quad)?;
|
|
///
|
|
/// // quad filter
|
|
/// let results: Result<Vec<Quad>,_> = store.quads_for_pattern(None, None, None, None).collect();
|
|
/// assert_eq!(vec![quad], results?);
|
|
///
|
|
/// // SPARQL query
|
|
/// if let QueryResult::Solutions(mut solutions) = store.query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default())? {
|
|
/// assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into()));
|
|
/// };
|
|
/// #
|
|
/// # };
|
|
/// # remove_dir_all("example.db")?;
|
|
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
|
|
/// ```
|
|
#[derive(Clone)]
|
|
pub struct SledStore {
|
|
id2str: Tree,
|
|
quads: Tree,
|
|
}
|
|
|
|
const SPOG_PREFIX: u8 = 1;
|
|
const POSG_PREFIX: u8 = 2;
|
|
const OSPG_PREFIX: u8 = 3;
|
|
const GSPO_PREFIX: u8 = 4;
|
|
const GPOS_PREFIX: u8 = 5;
|
|
const GOSP_PREFIX: u8 = 6;
|
|
|
|
//TODO: indexes for the default graph and indexes for the named graphs (no more Optional and space saving)
|
|
|
|
impl SledStore {
|
|
/// Opens a temporary `SledStore` that will be deleted after drop.
|
|
pub fn new() -> Result<Self, io::Error> {
|
|
Self::do_open(&Config::new().temporary(true))
|
|
}
|
|
|
|
/// Opens a `SledStore`
|
|
pub fn open(path: impl AsRef<Path>) -> Result<Self, io::Error> {
|
|
Self::do_open(&Config::new().path(path))
|
|
}
|
|
|
|
fn do_open(config: &Config) -> Result<Self, io::Error> {
|
|
let db = config.open()?;
|
|
Ok(Self {
|
|
id2str: db.open_tree("id2str")?,
|
|
quads: db.open_tree("quads")?,
|
|
})
|
|
}
|
|
|
|
/// Executes a [SPARQL 1.1 query](https://www.w3.org/TR/sparql11-query/).
|
|
///
|
|
/// See `MemoryStore` for a usage example.
|
|
pub fn query(
|
|
&self,
|
|
query: impl TryInto<Query, Error = impl Into<EvaluationError>>,
|
|
options: QueryOptions,
|
|
) -> Result<QueryResult, EvaluationError> {
|
|
self.prepare_query(query, options)?.exec()
|
|
}
|
|
|
|
/// Prepares a [SPARQL 1.1 query](https://www.w3.org/TR/sparql11-query/) and returns an object that could be used to execute it.
|
|
///
|
|
/// See `MemoryStore` for a usage example.
|
|
pub fn prepare_query(
|
|
&self,
|
|
query: impl TryInto<Query, Error = impl Into<EvaluationError>>,
|
|
options: QueryOptions,
|
|
) -> Result<SledPreparedQuery, EvaluationError> {
|
|
Ok(SledPreparedQuery(SimplePreparedQuery::new(
|
|
(*self).clone(),
|
|
query,
|
|
options,
|
|
)?))
|
|
}
|
|
|
|
/// Retrieves quads with a filter on each quad component
|
|
///
|
|
/// See `MemoryStore` for a usage example.
|
|
pub fn quads_for_pattern(
|
|
&self,
|
|
subject: Option<&NamedOrBlankNode>,
|
|
predicate: Option<&NamedNode>,
|
|
object: Option<&Term>,
|
|
graph_name: Option<&GraphName>,
|
|
) -> impl Iterator<Item = Result<Quad, io::Error>> {
|
|
let subject = subject.map(|s| s.into());
|
|
let predicate = predicate.map(|p| p.into());
|
|
let object = object.map(|o| o.into());
|
|
let graph_name = graph_name.map(|g| g.into());
|
|
let this = self.clone();
|
|
self.encoded_quads_for_pattern(subject, predicate, object, graph_name)
|
|
.map(move |quad| Ok(this.decode_quad(&quad?)?))
|
|
}
|
|
|
|
/// Checks if this store contains a given quad
|
|
pub fn contains(&self, quad: &Quad) -> Result<bool, io::Error> {
|
|
let quad = quad.into();
|
|
self.contains_encoded(&quad)
|
|
}
|
|
|
|
/// Returns the number of quads in the store
|
|
pub fn len(&self) -> usize {
|
|
self.quads.len() / 6
|
|
}
|
|
|
|
/// Returns if the store is empty
|
|
pub fn is_empty(&self) -> bool {
|
|
self.quads.is_empty()
|
|
}
|
|
|
|
/// Executes a transaction.
|
|
///
|
|
/// The transaction is executed if the given closure returns `Ok`.
|
|
/// Nothing is done if the closure returns `Err`.
|
|
///
|
|
/// Usage example:
|
|
/// ```
|
|
/// use oxigraph::SledStore;
|
|
/// use oxigraph::model::*;
|
|
/// use oxigraph::store::sled::SledConflictableTransactionError;
|
|
/// use std::convert::Infallible;
|
|
///
|
|
/// let store = SledStore::new()?;
|
|
///
|
|
/// let ex = NamedNode::new("http://example.com")?;
|
|
/// let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), None);
|
|
///
|
|
/// // transaction
|
|
/// store.transaction(|transaction| {
|
|
/// transaction.insert(&quad)?;
|
|
/// Ok(()) as Result<(),SledConflictableTransactionError<Infallible>>
|
|
/// })?;
|
|
///
|
|
/// // quad filter
|
|
/// assert!(store.contains(&quad)?);
|
|
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
|
|
/// ```
|
|
pub fn transaction<T, E>(
|
|
&self,
|
|
f: impl Fn(SledTransaction<'_>) -> Result<T, SledConflictableTransactionError<E>>,
|
|
) -> Result<T, SledTransactionError<E>> {
|
|
Ok((&self.id2str, &self.quads)
|
|
.transaction(move |(id2str, quads)| Ok(f(SledTransaction { id2str, quads })?))?)
|
|
}
|
|
|
|
/// Loads a graph file (i.e. triples) into the store
|
|
///
|
|
/// Warning: This functions saves the triples in batch. If the parsing fails in the middle of the file,
|
|
/// only a part of it may be written. Use a (memory greedy) transaction if you do not want that.
|
|
///
|
|
/// See `MemoryStore` for a usage example.
|
|
///
|
|
/// Errors related to parameter validation like the base IRI use the `INVALID_INPUT` error kind.
|
|
/// Errors related to a bad syntax in the loaded file use the `INVALID_DATA` error kind.
|
|
/// Errors related to data loading into the store use the other error kinds.
|
|
pub fn load_graph(
|
|
&self,
|
|
reader: impl BufRead,
|
|
format: GraphFormat,
|
|
to_graph_name: &GraphName,
|
|
base_iri: Option<&str>,
|
|
) -> Result<(), io::Error> {
|
|
let mut this = self;
|
|
load_graph(&mut this, reader, format, to_graph_name, base_iri)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Loads a dataset file (i.e. quads) into the store.
|
|
///
|
|
/// Warning: This functions saves the quads in batch. If the parsing fails in the middle of the file,
|
|
/// only a part of it may be written. Use a (memory greedy) transaction if you do not want that.
|
|
///
|
|
/// See `MemoryStore` for a usage example.
|
|
///
|
|
/// Errors related to parameter validation like the base IRI use the `INVALID_INPUT` error kind.
|
|
/// Errors related to a bad syntax in the loaded file use the `INVALID_DATA` error kind.
|
|
/// Errors related to data loading into the store use the other error kinds.
|
|
pub fn load_dataset(
|
|
&self,
|
|
reader: impl BufRead,
|
|
format: DatasetFormat,
|
|
base_iri: Option<&str>,
|
|
) -> Result<(), io::Error> {
|
|
let mut this = self;
|
|
load_dataset(&mut this, reader, format, base_iri)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Adds a quad to this store.
|
|
pub fn insert(&self, quad: &Quad) -> Result<(), io::Error> {
|
|
let mut this = self;
|
|
let quad = this.encode_quad(quad)?;
|
|
this.insert_encoded(&quad)
|
|
}
|
|
|
|
/// Removes a quad from this store.
|
|
pub fn remove(&self, quad: &Quad) -> Result<(), io::Error> {
|
|
let mut this = self;
|
|
let quad = quad.into();
|
|
this.remove_encoded(&quad)
|
|
}
|
|
|
|
/// Dumps a store graph into a file.
|
|
///
|
|
/// See `MemoryStore` for a usage example.
|
|
pub fn dump_graph(
|
|
&self,
|
|
writer: impl Write,
|
|
format: GraphFormat,
|
|
from_graph_name: &GraphName,
|
|
) -> Result<(), io::Error> {
|
|
dump_graph(
|
|
self.quads_for_pattern(None, None, None, Some(from_graph_name))
|
|
.map(|q| Ok(q?.into())),
|
|
writer,
|
|
format,
|
|
)
|
|
}
|
|
|
|
/// Dumps the store dataset into a file.
|
|
///
|
|
/// See `MemoryStore` for a usage example.
|
|
pub fn dump_dataset(&self, writer: impl Write, format: DatasetFormat) -> Result<(), io::Error> {
|
|
dump_dataset(
|
|
self.quads_for_pattern(None, None, None, None),
|
|
writer,
|
|
format,
|
|
)
|
|
}
|
|
|
|
fn contains_encoded(&self, quad: &EncodedQuad) -> Result<bool, io::Error> {
|
|
let mut buffer = Vec::with_capacity(4 * WRITTEN_TERM_MAX_SIZE);
|
|
write_spog_quad(&mut buffer, quad);
|
|
Ok(self.quads.contains_key(buffer)?)
|
|
}
|
|
|
|
fn quads(&self) -> DecodingQuadIterator {
|
|
self.inner_quads(&[SPOG_PREFIX])
|
|
}
|
|
|
|
fn quads_for_subject(&self, subject: EncodedTerm) -> DecodingQuadIterator {
|
|
self.inner_quads(encode_term(SPOG_PREFIX, subject))
|
|
}
|
|
|
|
fn quads_for_subject_predicate(
|
|
&self,
|
|
subject: EncodedTerm,
|
|
predicate: EncodedTerm,
|
|
) -> DecodingQuadIterator {
|
|
self.inner_quads(encode_term_pair(SPOG_PREFIX, subject, predicate))
|
|
}
|
|
|
|
fn quads_for_subject_predicate_object(
|
|
&self,
|
|
subject: EncodedTerm,
|
|
predicate: EncodedTerm,
|
|
object: EncodedTerm,
|
|
) -> DecodingQuadIterator {
|
|
self.inner_quads(encode_term_triple(SPOG_PREFIX, subject, predicate, object))
|
|
}
|
|
|
|
fn quads_for_subject_object(
|
|
&self,
|
|
subject: EncodedTerm,
|
|
object: EncodedTerm,
|
|
) -> DecodingQuadIterator {
|
|
self.inner_quads(encode_term_pair(OSPG_PREFIX, object, subject))
|
|
}
|
|
|
|
fn quads_for_predicate(&self, predicate: EncodedTerm) -> DecodingQuadIterator {
|
|
self.inner_quads(encode_term(POSG_PREFIX, predicate))
|
|
}
|
|
|
|
fn quads_for_predicate_object(
|
|
&self,
|
|
predicate: EncodedTerm,
|
|
object: EncodedTerm,
|
|
) -> DecodingQuadIterator {
|
|
self.inner_quads(encode_term_pair(POSG_PREFIX, predicate, object))
|
|
}
|
|
|
|
fn quads_for_object(&self, object: EncodedTerm) -> DecodingQuadIterator {
|
|
self.inner_quads(encode_term(OSPG_PREFIX, object))
|
|
}
|
|
|
|
fn quads_for_graph(&self, graph_name: EncodedTerm) -> DecodingQuadIterator {
|
|
self.inner_quads(encode_term(GSPO_PREFIX, graph_name))
|
|
}
|
|
|
|
fn quads_for_subject_graph(
|
|
&self,
|
|
subject: EncodedTerm,
|
|
graph_name: EncodedTerm,
|
|
) -> DecodingQuadIterator {
|
|
self.inner_quads(encode_term_pair(GSPO_PREFIX, graph_name, subject))
|
|
}
|
|
|
|
fn quads_for_subject_predicate_graph(
|
|
&self,
|
|
subject: EncodedTerm,
|
|
predicate: EncodedTerm,
|
|
graph_name: EncodedTerm,
|
|
) -> DecodingQuadIterator {
|
|
self.inner_quads(encode_term_triple(
|
|
GSPO_PREFIX,
|
|
graph_name,
|
|
subject,
|
|
predicate,
|
|
))
|
|
}
|
|
|
|
fn quads_for_subject_object_graph(
|
|
&self,
|
|
subject: EncodedTerm,
|
|
object: EncodedTerm,
|
|
graph_name: EncodedTerm,
|
|
) -> DecodingQuadIterator {
|
|
self.inner_quads(encode_term_triple(GOSP_PREFIX, graph_name, object, subject))
|
|
}
|
|
|
|
fn quads_for_predicate_graph(
|
|
&self,
|
|
predicate: EncodedTerm,
|
|
graph_name: EncodedTerm,
|
|
) -> DecodingQuadIterator {
|
|
self.inner_quads(encode_term_pair(GPOS_PREFIX, graph_name, predicate))
|
|
}
|
|
|
|
fn quads_for_predicate_object_graph(
|
|
&self,
|
|
predicate: EncodedTerm,
|
|
object: EncodedTerm,
|
|
graph_name: EncodedTerm,
|
|
) -> DecodingQuadIterator {
|
|
self.inner_quads(encode_term_triple(
|
|
GPOS_PREFIX,
|
|
graph_name,
|
|
predicate,
|
|
object,
|
|
))
|
|
}
|
|
|
|
fn quads_for_object_graph(
|
|
&self,
|
|
object: EncodedTerm,
|
|
graph_name: EncodedTerm,
|
|
) -> DecodingQuadIterator {
|
|
self.inner_quads(encode_term_pair(GPOS_PREFIX, graph_name, object))
|
|
}
|
|
|
|
fn inner_quads(&self, prefix: impl AsRef<[u8]>) -> DecodingQuadIterator {
|
|
DecodingQuadIterator {
|
|
iter: self.quads.scan_prefix(prefix),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for SledStore {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
for t in self.quads_for_pattern(None, None, None, None) {
|
|
writeln!(f, "{}", t.map_err(|_| fmt::Error)?)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl WithStoreError for SledStore {
|
|
type Error = io::Error;
|
|
}
|
|
|
|
impl StrLookup for SledStore {
|
|
fn get_str(&self, id: StrHash) -> Result<Option<String>, io::Error> {
|
|
self.id2str
|
|
.get(id.to_be_bytes())?
|
|
.map(|v| String::from_utf8(v.to_vec()))
|
|
.transpose()
|
|
.map_err(invalid_data_error)
|
|
}
|
|
}
|
|
|
|
impl ReadableEncodedStore for SledStore {
|
|
type QuadsIter = DecodingQuadIterator;
|
|
|
|
fn encoded_quads_for_pattern(
|
|
&self,
|
|
subject: Option<EncodedTerm>,
|
|
predicate: Option<EncodedTerm>,
|
|
object: Option<EncodedTerm>,
|
|
graph_name: Option<EncodedTerm>,
|
|
) -> DecodingQuadIterator {
|
|
match subject {
|
|
Some(subject) => match predicate {
|
|
Some(predicate) => match object {
|
|
Some(object) => match graph_name {
|
|
Some(graph_name) => self.inner_quads(encode_term_quad(
|
|
SPOG_PREFIX,
|
|
subject,
|
|
predicate,
|
|
object,
|
|
graph_name,
|
|
)),
|
|
None => self.quads_for_subject_predicate_object(subject, predicate, object),
|
|
},
|
|
None => match graph_name {
|
|
Some(graph_name) => {
|
|
self.quads_for_subject_predicate_graph(subject, predicate, graph_name)
|
|
}
|
|
None => self.quads_for_subject_predicate(subject, predicate),
|
|
},
|
|
},
|
|
None => match object {
|
|
Some(object) => match graph_name {
|
|
Some(graph_name) => {
|
|
self.quads_for_subject_object_graph(subject, object, graph_name)
|
|
}
|
|
None => self.quads_for_subject_object(subject, object),
|
|
},
|
|
None => match graph_name {
|
|
Some(graph_name) => self.quads_for_subject_graph(subject, graph_name),
|
|
None => self.quads_for_subject(subject),
|
|
},
|
|
},
|
|
},
|
|
None => match predicate {
|
|
Some(predicate) => match object {
|
|
Some(object) => match graph_name {
|
|
Some(graph_name) => {
|
|
self.quads_for_predicate_object_graph(predicate, object, graph_name)
|
|
}
|
|
None => self.quads_for_predicate_object(predicate, object),
|
|
},
|
|
None => match graph_name {
|
|
Some(graph_name) => self.quads_for_predicate_graph(predicate, graph_name),
|
|
None => self.quads_for_predicate(predicate),
|
|
},
|
|
},
|
|
None => match object {
|
|
Some(object) => match graph_name {
|
|
Some(graph_name) => self.quads_for_object_graph(object, graph_name),
|
|
None => self.quads_for_object(object),
|
|
},
|
|
None => match graph_name {
|
|
Some(graph_name) => self.quads_for_graph(graph_name),
|
|
None => self.quads(),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> WithStoreError for &'a SledStore {
|
|
type Error = io::Error;
|
|
}
|
|
|
|
impl<'a> StrContainer for &'a SledStore {
|
|
fn insert_str(&mut self, value: &str) -> Result<StrHash, io::Error> {
|
|
let key = StrHash::new(value);
|
|
self.id2str.insert(key.to_be_bytes().as_ref(), value)?;
|
|
Ok(key)
|
|
}
|
|
}
|
|
|
|
impl<'a> WritableEncodedStore for &'a SledStore {
|
|
fn insert_encoded(&mut self, quad: &EncodedQuad) -> Result<(), io::Error> {
|
|
let mut buffer = Vec::with_capacity(4 * WRITTEN_TERM_MAX_SIZE + 1);
|
|
|
|
write_spog_quad(&mut buffer, quad);
|
|
self.quads.insert(buffer.as_slice(), &[])?;
|
|
buffer.clear();
|
|
|
|
write_posg_quad(&mut buffer, quad);
|
|
self.quads.insert(buffer.as_slice(), &[])?;
|
|
buffer.clear();
|
|
|
|
write_ospg_quad(&mut buffer, quad);
|
|
self.quads.insert(buffer.as_slice(), &[])?;
|
|
buffer.clear();
|
|
|
|
write_gspo_quad(&mut buffer, quad);
|
|
self.quads.insert(buffer.as_slice(), &[])?;
|
|
buffer.clear();
|
|
|
|
write_gpos_quad(&mut buffer, quad);
|
|
self.quads.insert(buffer.as_slice(), &[])?;
|
|
buffer.clear();
|
|
|
|
write_gosp_quad(&mut buffer, quad);
|
|
self.quads.insert(buffer.as_slice(), &[])?;
|
|
buffer.clear();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn remove_encoded(&mut self, quad: &EncodedQuad) -> Result<(), io::Error> {
|
|
let mut buffer = Vec::with_capacity(4 * WRITTEN_TERM_MAX_SIZE + 1);
|
|
|
|
write_spog_quad(&mut buffer, quad);
|
|
self.quads.remove(buffer.as_slice())?;
|
|
buffer.clear();
|
|
|
|
write_posg_quad(&mut buffer, quad);
|
|
self.quads.remove(buffer.as_slice())?;
|
|
buffer.clear();
|
|
|
|
write_ospg_quad(&mut buffer, quad);
|
|
self.quads.remove(buffer.as_slice())?;
|
|
buffer.clear();
|
|
|
|
write_gspo_quad(&mut buffer, quad);
|
|
self.quads.remove(buffer.as_slice())?;
|
|
buffer.clear();
|
|
|
|
write_gpos_quad(&mut buffer, quad);
|
|
self.quads.remove(buffer.as_slice())?;
|
|
buffer.clear();
|
|
|
|
write_gosp_quad(&mut buffer, quad);
|
|
self.quads.remove(buffer.as_slice())?;
|
|
buffer.clear();
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Allows inserting and deleting quads during a transaction with the `SeldStore`.
|
|
pub struct SledTransaction<'a> {
|
|
quads: &'a TransactionalTree,
|
|
id2str: &'a TransactionalTree,
|
|
}
|
|
|
|
impl SledTransaction<'_> {
|
|
/// Loads a graph file (i.e. triples) into the store during the transaction.
|
|
///
|
|
/// Warning: Because the load happens during a transaction,
|
|
/// the full file content might be temporarily stored in main memory.
|
|
/// Do not use for big files.
|
|
///
|
|
/// See `MemoryTransaction` for a usage example.
|
|
///
|
|
/// Errors related to parameter validation like the base IRI use the `INVALID_INPUT` error kind.
|
|
/// Errors related to a bad syntax in the loaded file use the `INVALID_DATA` error kind.
|
|
pub fn load_graph(
|
|
&self,
|
|
reader: impl BufRead,
|
|
format: GraphFormat,
|
|
to_graph_name: &GraphName,
|
|
base_iri: Option<&str>,
|
|
) -> Result<(), SledUnabortableTransactionError> {
|
|
let mut this = self;
|
|
load_graph(&mut this, reader, format, to_graph_name, base_iri)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Loads a dataset file (i.e. quads) into the store. into the store during the transaction.
|
|
///
|
|
/// Warning: Because the load happens during a transaction,
|
|
/// the full file content might be temporarily stored in main memory.
|
|
/// Do not use for big files.
|
|
///
|
|
/// See `MemoryTransaction` for a usage example.
|
|
///
|
|
/// Errors related to parameter validation like the base IRI use the `INVALID_INPUT` error kind.
|
|
/// Errors related to a bad syntax in the loaded file use the `INVALID_DATA` error kind.
|
|
pub fn load_dataset(
|
|
&self,
|
|
reader: impl BufRead,
|
|
format: DatasetFormat,
|
|
base_iri: Option<&str>,
|
|
) -> Result<(), SledUnabortableTransactionError> {
|
|
let mut this = self;
|
|
load_dataset(&mut this, reader, format, base_iri)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Adds a quad to this store during the transaction.
|
|
pub fn insert(&self, quad: &Quad) -> Result<(), SledUnabortableTransactionError> {
|
|
let mut this = self;
|
|
let quad = this.encode_quad(quad)?;
|
|
this.insert_encoded(&quad)
|
|
}
|
|
|
|
/// Removes a quad from this store during the transaction.
|
|
pub fn remove(&self, quad: &Quad) -> Result<(), SledUnabortableTransactionError> {
|
|
let mut this = self;
|
|
let quad = quad.into();
|
|
this.remove_encoded(&quad)
|
|
}
|
|
}
|
|
|
|
impl<'a> WithStoreError for &'a SledTransaction<'a> {
|
|
type Error = SledUnabortableTransactionError;
|
|
}
|
|
|
|
impl<'a> StrContainer for &'a SledTransaction<'a> {
|
|
fn insert_str(&mut self, value: &str) -> Result<StrHash, SledUnabortableTransactionError> {
|
|
let key = StrHash::new(value);
|
|
self.id2str.insert(key.to_be_bytes().as_ref(), value)?;
|
|
Ok(key)
|
|
}
|
|
}
|
|
|
|
impl<'a> WritableEncodedStore for &'a SledTransaction<'a> {
|
|
fn insert_encoded(
|
|
&mut self,
|
|
quad: &EncodedQuad,
|
|
) -> Result<(), SledUnabortableTransactionError> {
|
|
let mut buffer = Vec::with_capacity(4 * WRITTEN_TERM_MAX_SIZE + 1);
|
|
|
|
write_spog_quad(&mut buffer, quad);
|
|
self.quads.insert(buffer.as_slice(), &[])?;
|
|
buffer.clear();
|
|
|
|
write_posg_quad(&mut buffer, quad);
|
|
self.quads.insert(buffer.as_slice(), &[])?;
|
|
buffer.clear();
|
|
|
|
write_ospg_quad(&mut buffer, quad);
|
|
self.quads.insert(buffer.as_slice(), &[])?;
|
|
buffer.clear();
|
|
|
|
write_gspo_quad(&mut buffer, quad);
|
|
self.quads.insert(buffer.as_slice(), &[])?;
|
|
buffer.clear();
|
|
|
|
write_gpos_quad(&mut buffer, quad);
|
|
self.quads.insert(buffer.as_slice(), &[])?;
|
|
buffer.clear();
|
|
|
|
write_gosp_quad(&mut buffer, quad);
|
|
self.quads.insert(buffer.as_slice(), &[])?;
|
|
buffer.clear();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn remove_encoded(
|
|
&mut self,
|
|
quad: &EncodedQuad,
|
|
) -> Result<(), SledUnabortableTransactionError> {
|
|
let mut buffer = Vec::with_capacity(4 * WRITTEN_TERM_MAX_SIZE + 1);
|
|
|
|
write_spog_quad(&mut buffer, quad);
|
|
self.quads.remove(buffer.as_slice())?;
|
|
buffer.clear();
|
|
|
|
write_posg_quad(&mut buffer, quad);
|
|
self.quads.remove(buffer.as_slice())?;
|
|
buffer.clear();
|
|
|
|
write_ospg_quad(&mut buffer, quad);
|
|
self.quads.remove(buffer.as_slice())?;
|
|
buffer.clear();
|
|
|
|
write_gspo_quad(&mut buffer, quad);
|
|
self.quads.remove(buffer.as_slice())?;
|
|
buffer.clear();
|
|
|
|
write_gpos_quad(&mut buffer, quad);
|
|
self.quads.remove(buffer.as_slice())?;
|
|
buffer.clear();
|
|
|
|
write_gosp_quad(&mut buffer, quad);
|
|
self.quads.remove(buffer.as_slice())?;
|
|
buffer.clear();
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Error returned by a Sled transaction
|
|
#[derive(Debug)]
|
|
pub enum SledTransactionError<T> {
|
|
/// An failure returned by the API user that have aborted the transaction
|
|
Abort(T),
|
|
/// A storage related error
|
|
Storage(io::Error),
|
|
}
|
|
|
|
impl<T: fmt::Display> fmt::Display for SledTransactionError<T> {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Abort(e) => e.fmt(f),
|
|
Self::Storage(e) => e.fmt(f),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Error + 'static> Error for SledTransactionError<T> {
|
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
match self {
|
|
Self::Abort(e) => Some(e),
|
|
Self::Storage(e) => Some(e),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> From<TransactionError<T>> for SledTransactionError<T> {
|
|
fn from(e: TransactionError<T>) -> Self {
|
|
match e {
|
|
TransactionError::Abort(e) => Self::Abort(e),
|
|
TransactionError::Storage(e) => Self::Storage(e.into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An error returned from the transaction methods.
|
|
/// Should be returned as it is
|
|
#[derive(Debug)]
|
|
pub enum SledUnabortableTransactionError {
|
|
#[doc(hidden)]
|
|
Conflict,
|
|
/// A regular error
|
|
Storage(io::Error),
|
|
}
|
|
|
|
impl fmt::Display for SledUnabortableTransactionError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Conflict => write!(f, "Transaction conflict"),
|
|
Self::Storage(e) => e.fmt(f),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Error for SledUnabortableTransactionError {
|
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
match self {
|
|
Self::Storage(e) => Some(e),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<SledUnabortableTransactionError> for EvaluationError {
|
|
fn from(e: SledUnabortableTransactionError) -> Self {
|
|
match e {
|
|
SledUnabortableTransactionError::Storage(e) => Self::Io(e),
|
|
SledUnabortableTransactionError::Conflict => Self::Conflict,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<StoreOrParseError<SledUnabortableTransactionError, io::Error>>
|
|
for SledUnabortableTransactionError
|
|
{
|
|
fn from(e: StoreOrParseError<SledUnabortableTransactionError, io::Error>) -> Self {
|
|
match e {
|
|
StoreOrParseError::Store(e) => e,
|
|
StoreOrParseError::Parse(e) => Self::Storage(e),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<UnabortableTransactionError> for SledUnabortableTransactionError {
|
|
fn from(e: UnabortableTransactionError) -> Self {
|
|
match e {
|
|
UnabortableTransactionError::Storage(e) => Self::Storage(e.into()),
|
|
UnabortableTransactionError::Conflict => Self::Conflict,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An error returned from the transaction closure
|
|
#[derive(Debug)]
|
|
pub enum SledConflictableTransactionError<T> {
|
|
/// A failure returned by the user that will abort the transaction
|
|
Abort(T),
|
|
#[doc(hidden)]
|
|
Conflict,
|
|
/// A storage related error
|
|
Storage(io::Error),
|
|
}
|
|
|
|
impl<T: fmt::Display> fmt::Display for SledConflictableTransactionError<T> {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::Conflict => write!(f, "Transaction conflict"),
|
|
Self::Storage(e) => e.fmt(f),
|
|
Self::Abort(e) => e.fmt(f),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Error + 'static> Error for SledConflictableTransactionError<T> {
|
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
match self {
|
|
Self::Abort(e) => Some(e),
|
|
Self::Storage(e) => Some(e),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> From<SledUnabortableTransactionError> for SledConflictableTransactionError<T> {
|
|
fn from(e: SledUnabortableTransactionError) -> Self {
|
|
match e {
|
|
SledUnabortableTransactionError::Storage(e) => Self::Storage(e),
|
|
SledUnabortableTransactionError::Conflict => Self::Conflict,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> From<SledConflictableTransactionError<T>> for ConflictableTransactionError<T> {
|
|
fn from(e: SledConflictableTransactionError<T>) -> Self {
|
|
match e {
|
|
SledConflictableTransactionError::Abort(e) => ConflictableTransactionError::Abort(e),
|
|
SledConflictableTransactionError::Conflict => ConflictableTransactionError::Conflict,
|
|
SledConflictableTransactionError::Storage(e) => {
|
|
ConflictableTransactionError::Storage(e.into())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A prepared [SPARQL query](https://www.w3.org/TR/sparql11-query/) for the `SledStore`.
|
|
pub struct SledPreparedQuery(SimplePreparedQuery<SledStore>);
|
|
|
|
impl SledPreparedQuery {
|
|
/// Evaluates the query and returns its results
|
|
pub fn exec(&self) -> Result<QueryResult, EvaluationError> {
|
|
self.0.exec()
|
|
}
|
|
}
|
|
|
|
fn encode_term(prefix: u8, t: EncodedTerm) -> Vec<u8> {
|
|
let mut vec = Vec::with_capacity(WRITTEN_TERM_MAX_SIZE + 1);
|
|
vec.push(prefix);
|
|
write_term(&mut vec, t);
|
|
vec
|
|
}
|
|
|
|
fn encode_term_pair(prefix: u8, t1: EncodedTerm, t2: EncodedTerm) -> Vec<u8> {
|
|
let mut vec = Vec::with_capacity(2 * WRITTEN_TERM_MAX_SIZE + 1);
|
|
vec.push(prefix);
|
|
write_term(&mut vec, t1);
|
|
write_term(&mut vec, t2);
|
|
vec
|
|
}
|
|
|
|
fn encode_term_triple(prefix: u8, t1: EncodedTerm, t2: EncodedTerm, t3: EncodedTerm) -> Vec<u8> {
|
|
let mut vec = Vec::with_capacity(3 * WRITTEN_TERM_MAX_SIZE + 1);
|
|
vec.push(prefix);
|
|
write_term(&mut vec, t1);
|
|
write_term(&mut vec, t2);
|
|
write_term(&mut vec, t3);
|
|
vec
|
|
}
|
|
|
|
fn encode_term_quad(
|
|
prefix: u8,
|
|
t1: EncodedTerm,
|
|
t2: EncodedTerm,
|
|
t3: EncodedTerm,
|
|
t4: EncodedTerm,
|
|
) -> Vec<u8> {
|
|
let mut vec = Vec::with_capacity(4 * WRITTEN_TERM_MAX_SIZE + 1);
|
|
vec.push(prefix);
|
|
write_term(&mut vec, t1);
|
|
write_term(&mut vec, t2);
|
|
write_term(&mut vec, t3);
|
|
write_term(&mut vec, t4);
|
|
vec
|
|
}
|
|
|
|
pub(crate) struct DecodingQuadIterator {
|
|
iter: Iter,
|
|
}
|
|
|
|
impl Iterator for DecodingQuadIterator {
|
|
type Item = Result<EncodedQuad, io::Error>;
|
|
|
|
fn next(&mut self) -> Option<Result<EncodedQuad, io::Error>> {
|
|
Some(match self.iter.next()? {
|
|
Ok((encoded, _)) => decode_quad(&encoded),
|
|
Err(error) => Err(error.into()),
|
|
})
|
|
}
|
|
}
|
|
|
|
fn write_spog_quad(sink: &mut Vec<u8>, quad: &EncodedQuad) {
|
|
sink.push(SPOG_PREFIX);
|
|
write_term(sink, quad.subject);
|
|
write_term(sink, quad.predicate);
|
|
write_term(sink, quad.object);
|
|
write_term(sink, quad.graph_name);
|
|
}
|
|
|
|
fn write_posg_quad(sink: &mut Vec<u8>, quad: &EncodedQuad) {
|
|
sink.push(POSG_PREFIX);
|
|
write_term(sink, quad.predicate);
|
|
write_term(sink, quad.object);
|
|
write_term(sink, quad.subject);
|
|
write_term(sink, quad.graph_name);
|
|
}
|
|
|
|
fn write_ospg_quad(sink: &mut Vec<u8>, quad: &EncodedQuad) {
|
|
sink.push(OSPG_PREFIX);
|
|
write_term(sink, quad.object);
|
|
write_term(sink, quad.subject);
|
|
write_term(sink, quad.predicate);
|
|
write_term(sink, quad.graph_name);
|
|
}
|
|
|
|
fn write_gspo_quad(sink: &mut Vec<u8>, quad: &EncodedQuad) {
|
|
sink.push(GSPO_PREFIX);
|
|
write_term(sink, quad.graph_name);
|
|
write_term(sink, quad.subject);
|
|
write_term(sink, quad.predicate);
|
|
write_term(sink, quad.object);
|
|
}
|
|
|
|
fn write_gpos_quad(sink: &mut Vec<u8>, quad: &EncodedQuad) {
|
|
sink.push(GPOS_PREFIX);
|
|
write_term(sink, quad.graph_name);
|
|
write_term(sink, quad.predicate);
|
|
write_term(sink, quad.object);
|
|
write_term(sink, quad.subject);
|
|
}
|
|
|
|
fn write_gosp_quad(sink: &mut Vec<u8>, quad: &EncodedQuad) {
|
|
sink.push(GOSP_PREFIX);
|
|
write_term(sink, quad.graph_name);
|
|
write_term(sink, quad.object);
|
|
write_term(sink, quad.subject);
|
|
write_term(sink, quad.predicate);
|
|
}
|
|
|
|
fn decode_quad(encoded: &[u8]) -> Result<EncodedQuad, io::Error> {
|
|
let mut cursor = Cursor::new(&encoded[1..]);
|
|
match encoded[0] {
|
|
SPOG_PREFIX => Ok(cursor.read_spog_quad()?),
|
|
POSG_PREFIX => Ok(cursor.read_posg_quad()?),
|
|
OSPG_PREFIX => Ok(cursor.read_ospg_quad()?),
|
|
GSPO_PREFIX => Ok(cursor.read_gspo_quad()?),
|
|
GPOS_PREFIX => Ok(cursor.read_gpos_quad()?),
|
|
GOSP_PREFIX => Ok(cursor.read_gosp_quad()?),
|
|
_ => Err(invalid_data_error(format!(
|
|
"Invalid quad type identifier: {}",
|
|
encoded[0]
|
|
))),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn store() -> Result<(), io::Error> {
|
|
use crate::model::*;
|
|
|
|
let main_s = NamedOrBlankNode::from(BlankNode::default());
|
|
let main_p = NamedNode::new("http://example.com").unwrap();
|
|
let main_o = Term::from(Literal::from(1));
|
|
|
|
let main_quad = Quad::new(main_s.clone(), main_p.clone(), main_o.clone(), None);
|
|
let all_o = vec![
|
|
Quad::new(main_s.clone(), main_p.clone(), Literal::from(0), None),
|
|
Quad::new(main_s.clone(), main_p.clone(), main_o.clone(), None),
|
|
Quad::new(main_s.clone(), main_p.clone(), Literal::from(2), None),
|
|
];
|
|
|
|
let store = SledStore::new()?;
|
|
store.insert(&main_quad)?;
|
|
for t in &all_o {
|
|
store.insert(t)?;
|
|
}
|
|
|
|
let target = vec![main_quad];
|
|
assert_eq!(
|
|
store
|
|
.quads_for_pattern(None, None, None, None)
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
all_o
|
|
);
|
|
assert_eq!(
|
|
store
|
|
.quads_for_pattern(Some(&main_s), None, None, None)
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
all_o
|
|
);
|
|
assert_eq!(
|
|
store
|
|
.quads_for_pattern(Some(&main_s), Some(&main_p), None, None)
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
all_o
|
|
);
|
|
assert_eq!(
|
|
store
|
|
.quads_for_pattern(Some(&main_s), Some(&main_p), Some(&main_o), None)
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
target
|
|
);
|
|
assert_eq!(
|
|
store
|
|
.quads_for_pattern(
|
|
Some(&main_s),
|
|
Some(&main_p),
|
|
Some(&main_o),
|
|
Some(&GraphName::DefaultGraph)
|
|
)
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
target
|
|
);
|
|
assert_eq!(
|
|
store
|
|
.quads_for_pattern(
|
|
Some(&main_s),
|
|
Some(&main_p),
|
|
None,
|
|
Some(&GraphName::DefaultGraph)
|
|
)
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
all_o
|
|
);
|
|
assert_eq!(
|
|
store
|
|
.quads_for_pattern(Some(&main_s), None, Some(&main_o), None)
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
target
|
|
);
|
|
assert_eq!(
|
|
store
|
|
.quads_for_pattern(
|
|
Some(&main_s),
|
|
None,
|
|
Some(&main_o),
|
|
Some(&GraphName::DefaultGraph)
|
|
)
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
target
|
|
);
|
|
assert_eq!(
|
|
store
|
|
.quads_for_pattern(Some(&main_s), None, None, Some(&GraphName::DefaultGraph))
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
all_o
|
|
);
|
|
assert_eq!(
|
|
store
|
|
.quads_for_pattern(None, Some(&main_p), None, None)
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
all_o
|
|
);
|
|
assert_eq!(
|
|
store
|
|
.quads_for_pattern(None, Some(&main_p), Some(&main_o), None)
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
target
|
|
);
|
|
assert_eq!(
|
|
store
|
|
.quads_for_pattern(None, None, Some(&main_o), None)
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
target
|
|
);
|
|
assert_eq!(
|
|
store
|
|
.quads_for_pattern(None, None, None, Some(&GraphName::DefaultGraph))
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
all_o
|
|
);
|
|
assert_eq!(
|
|
store
|
|
.quads_for_pattern(
|
|
None,
|
|
Some(&main_p),
|
|
Some(&main_o),
|
|
Some(&GraphName::DefaultGraph)
|
|
)
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
target
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|