Adds an other store based on Sled

pull/35/head
Tpt 4 years ago
parent ec67f4928a
commit dc08e181a8
  1. 5
      lib/Cargo.toml
  2. 98
      lib/benches/store.rs
  3. 7
      lib/src/error.rs
  4. 10
      lib/src/lib.rs
  5. 4
      lib/src/store/mod.rs
  6. 637
      lib/src/store/sled.rs

@ -14,6 +14,7 @@ edition = "2018"
[dependencies] [dependencies]
lazy_static = "1" lazy_static = "1"
rocksdb = { version = "0.14", optional = true } rocksdb = { version = "0.14", optional = true }
sled = { version = "0.31", optional = true }
quick-xml = "0.18" quick-xml = "0.18"
rand = "0.7" rand = "0.7"
md-5 = "0.8" md-5 = "0.8"
@ -43,6 +44,10 @@ anyhow = "1"
[target.'cfg(target_arch = "wasm32")'.dev-dependencies] [target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3" wasm-bindgen-test = "0.3"
[[bench]]
name = "store"
harness = false
[[bench]] [[bench]]
name = "sparql_query" name = "sparql_query"
harness = false harness = false

@ -0,0 +1,98 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use oxigraph::model::*;
use oxigraph::*;
use rand::random;
use std::env::temp_dir;
use std::fs::remove_dir_all;
criterion_group!(
store_load,
memory_load_bench,
sled_load_bench,
rocksdb_load_bench
);
criterion_main!(store_load);
fn memory_load_bench(c: &mut Criterion) {
let mut group = c.benchmark_group("memory");
group.nresamples(10);
group.sample_size(10);
for size in [100, 1_000, 10_000].iter() {
group.throughput(Throughput::Elements(*size as u64));
let quads = create_quads(*size);
group.bench_function(BenchmarkId::from_parameter(size), |b| {
b.iter(|| {
let store = MemoryStore::new();
for quad in &quads {
store.insert(quad).unwrap();
}
});
});
}
group.finish();
}
fn sled_load_bench(c: &mut Criterion) {
let mut group = c.benchmark_group("sled");
group.nresamples(10);
group.sample_size(10);
for size in [100, 1_000, 10_000].iter() {
group.throughput(Throughput::Elements(*size as u64));
let quads = create_quads(*size);
group.bench_function(BenchmarkId::from_parameter(size), |b| {
b.iter(|| {
let store = SledStore::new().unwrap();
for quad in &quads {
store.insert(quad).unwrap();
}
});
});
}
group.finish();
}
fn rocksdb_load_bench(c: &mut Criterion) {
let mut group = c.benchmark_group("rocksdb");
group.nresamples(10);
group.sample_size(10);
let temp_dir = temp_dir();
for size in [100, 1_000, 10_000].iter() {
group.throughput(Throughput::Elements(*size as u64));
let quads = create_quads(*size);
group.bench_function(BenchmarkId::from_parameter(size), |b| {
b.iter(|| {
let mut dir = temp_dir.clone();
dir.push(random::<u64>().to_string());
let store = RocksDbStore::open(&dir).unwrap();
for quad in &quads {
store.insert(quad).unwrap();
}
remove_dir_all(&dir).unwrap();
});
});
}
group.finish();
}
fn create_quads(size: u64) -> Vec<Quad> {
(0..size)
.map(|_| {
Quad::new(
NamedNode::new_unchecked(format!(
"http://example.com/id/{}",
random::<u64>() % size
)),
NamedNode::new_unchecked(format!(
"http://example.com/id/{}",
random::<u64>() % size
)),
NamedNode::new_unchecked(format!(
"http://example.com/id/{}",
random::<u64>() % size
)),
None,
)
})
.collect()
}

@ -132,3 +132,10 @@ impl From<rocksdb::Error> for Error {
Self::wrap(error) Self::wrap(error)
} }
} }
#[cfg(feature = "sled")]
impl From<sled::Error> for Error {
fn from(error: sled::Error) -> Self {
Self::wrap(error)
}
}

@ -2,9 +2,15 @@
//! //!
//! Its goal is to provide a compliant, safe and fast graph database. //! Its goal is to provide a compliant, safe and fast graph database.
//! //!
//! It currently provides two `Store` implementation providing [SPARQL 1.1 query](https://www.w3.org/TR/sparql11-query/) capability: //! It currently provides three `Store` implementation providing [SPARQL 1.1 query](https://www.w3.org/TR/sparql11-query/) capability:
//! * `MemoryStore`: a simple in memory implementation. //! * `MemoryStore`: a simple in memory implementation.
//! * `RocksDbStore`: a file system implementation based on the [RocksDB](https://rocksdb.org/) key-value store. //! * `RocksDbStore`: a file system implementation based on the [RocksDB](https://rocksdb.org/) key-value store.
//! It requires the `"rocksdb"` feature to be activated.
//! It also requires the clang](https://clang.llvm.org/) compiler to be installed.
//! * `Sled`: an other file system implementation based on the [Sled](https://sled.rs/) key-value store.
//! It requires the `"sled"` feature to be activated.
//! Sled is much faster to build than RockDB and does not require a C++ compiler.
//! However Sled is still in heavy developpment, less tested and data load seems much slower than RocksDB.
//! //!
//! Usage example with the `MemoryStore`: //! Usage example with the `MemoryStore`:
//! //!
@ -116,6 +122,8 @@ pub use crate::store::MemoryTransaction;
pub use crate::store::RocksDbStore; pub use crate::store::RocksDbStore;
#[cfg(feature = "rocksdb")] #[cfg(feature = "rocksdb")]
pub use crate::store::RocksDbTransaction; pub use crate::store::RocksDbTransaction;
#[cfg(feature = "sled")]
pub use crate::store::SledStore;
pub use crate::syntax::DatasetSyntax; pub use crate::syntax::DatasetSyntax;
pub use crate::syntax::FileSyntax; pub use crate::syntax::FileSyntax;
pub use crate::syntax::GraphSyntax; pub use crate::syntax::GraphSyntax;

@ -4,6 +4,8 @@ mod memory;
pub(crate) mod numeric_encoder; pub(crate) mod numeric_encoder;
#[cfg(feature = "rocksdb")] #[cfg(feature = "rocksdb")]
mod rocksdb; mod rocksdb;
#[cfg(feature = "sled")]
mod sled;
use crate::sparql::GraphPattern; use crate::sparql::GraphPattern;
pub use crate::store::memory::MemoryStore; pub use crate::store::memory::MemoryStore;
@ -12,6 +14,8 @@ pub use crate::store::memory::MemoryTransaction;
pub use crate::store::rocksdb::RocksDbStore; pub use crate::store::rocksdb::RocksDbStore;
#[cfg(feature = "rocksdb")] #[cfg(feature = "rocksdb")]
pub use crate::store::rocksdb::RocksDbTransaction; pub use crate::store::rocksdb::RocksDbTransaction;
#[cfg(feature = "sled")]
pub use crate::store::sled::SledStore;
use crate::model::*; use crate::model::*;
use crate::store::numeric_encoder::*; use crate::store::numeric_encoder::*;

@ -0,0 +1,637 @@
use crate::model::*;
use crate::sparql::{GraphPattern, PreparedQuery, QueryOptions, SimplePreparedQuery};
use crate::store::numeric_encoder::*;
use crate::store::{load_dataset, load_graph, ReadableEncodedStore, WritableEncodedStore};
use crate::{DatasetSyntax, GraphSyntax, Result};
use sled::{Config, Iter, Tree};
use std::io::BufRead;
use std::path::Path;
use std::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::model::*;
/// use oxigraph::{Result, SledStore};
/// use oxigraph::sparql::{PreparedQuery, QueryOptions, QueryResult};
/// # use std::fs::remove_dir_all;
///
/// # {
/// let store = SledStore::open("example.db")?;
///
/// // insertion
/// let ex = NamedNode::parse("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
/// let prepared_query = store.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default())?;
/// let results = prepared_query.exec()?;
/// if let QueryResult::Bindings(results) = results {
/// assert_eq!(results.into_values_iter().next().unwrap()?[0], Some(ex.into()));
/// }
/// #
/// # }
/// # remove_dir_all("example.db")?;
/// # Result::Ok(())
/// ```
#[derive(Clone)]
pub struct SledStore {
id2str: Tree,
spog: Tree,
posg: Tree,
ospg: Tree,
gspo: Tree,
gpos: Tree,
gosp: Tree,
}
//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> {
Self::do_open(Config::new().temporary(true))
}
/// Opens a `SledStore`
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
Self::do_open(Config::new().path(path))
}
fn do_open(config: Config) -> Result<Self> {
let db = config.open()?;
let new = Self {
id2str: db.open_tree("id2str")?,
spog: db.open_tree("spog")?,
posg: db.open_tree("posg")?,
ospg: db.open_tree("ospg")?,
gspo: db.open_tree("gspo")?,
gpos: db.open_tree("gpos")?,
gosp: db.open_tree("gosp")?,
};
(&new).set_first_strings()?;
Ok(new)
}
/// 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<'a>(
&'a self,
query: &str,
options: QueryOptions<'_>,
) -> Result<impl PreparedQuery + 'a> {
SimplePreparedQuery::new((*self).clone(), query, options)
}
/// This is similar to `prepare_query`, but useful if a SPARQL query has already been parsed, which is the case when building `ServiceHandler`s for federated queries with `SERVICE` clauses. For examples, look in the tests.
pub fn prepare_query_from_pattern<'a>(
&'a self,
graph_pattern: &GraphPattern,
options: QueryOptions<'_>,
) -> Result<impl PreparedQuery + 'a> {
SimplePreparedQuery::new_from_pattern((*self).clone(), graph_pattern, options)
}
/// Retrieves quads with a filter on each quad component
///
/// See `MemoryStore` for a usage example.
#[allow(clippy::option_option)]
pub fn quads_for_pattern(
&self,
subject: Option<&NamedOrBlankNode>,
predicate: Option<&NamedNode>,
object: Option<&Term>,
graph_name: Option<Option<&NamedOrBlankNode>>,
) -> impl Iterator<Item = Result<Quad>> {
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.map_or(ENCODED_DEFAULT_GRAPH, |g| g.into()));
let this = self.clone();
self.encoded_quads_for_pattern_inner(subject, predicate, object, graph_name)
.map(move |quad| this.decode_quad(&quad?))
}
/// Checks if this store contains a given quad
pub fn contains(&self, quad: &Quad) -> Result<bool> {
let quad = quad.into();
self.contains_encoded(&quad)
}
/// 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.
pub fn load_graph(
&self,
reader: impl BufRead,
syntax: GraphSyntax,
to_graph_name: Option<&NamedOrBlankNode>,
base_iri: Option<&str>,
) -> Result<()> {
let mut store = self;
load_graph(&mut store, reader, syntax, to_graph_name, base_iri)
}
/// 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.
pub fn load_dataset(
&self,
reader: impl BufRead,
syntax: DatasetSyntax,
base_iri: Option<&str>,
) -> Result<()> {
let mut store = self;
load_dataset(&mut store, reader, syntax, base_iri)
}
/// Adds a quad to this store.
pub fn insert(&self, quad: &Quad) -> Result<()> {
let mut store = self;
let quad = store.encode_quad(quad)?;
store.insert_encoded(&quad)
}
/// Removes a quad from this store.
pub fn remove(&self, quad: &Quad) -> Result<()> {
let mut store = self;
let quad = quad.into();
store.remove_encoded(&quad)
}
fn contains_encoded(&self, quad: &EncodedQuad) -> Result<bool> {
let mut buffer = Vec::with_capacity(4 * WRITTEN_TERM_MAX_SIZE);
write_spog_quad(&mut buffer, quad);
Ok(self.spog.contains_key(buffer)?)
}
fn encoded_quads_for_pattern_inner(
&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
.spog_quads(encode_term_quad(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(),
},
},
},
}
}
fn quads(&self) -> DecodingQuadIterator {
self.spog_quads(Vec::default())
}
fn quads_for_subject(&self, subject: EncodedTerm) -> DecodingQuadIterator {
self.spog_quads(encode_term(subject))
}
fn quads_for_subject_predicate(
&self,
subject: EncodedTerm,
predicate: EncodedTerm,
) -> DecodingQuadIterator {
self.spog_quads(encode_term_pair(subject, predicate))
}
fn quads_for_subject_predicate_object(
&self,
subject: EncodedTerm,
predicate: EncodedTerm,
object: EncodedTerm,
) -> DecodingQuadIterator {
self.spog_quads(encode_term_triple(subject, predicate, object))
}
fn quads_for_subject_object(
&self,
subject: EncodedTerm,
object: EncodedTerm,
) -> DecodingQuadIterator {
self.ospg_quads(encode_term_pair(object, subject))
}
fn quads_for_predicate(&self, predicate: EncodedTerm) -> DecodingQuadIterator {
self.posg_quads(encode_term(predicate))
}
fn quads_for_predicate_object(
&self,
predicate: EncodedTerm,
object: EncodedTerm,
) -> DecodingQuadIterator {
self.posg_quads(encode_term_pair(predicate, object))
}
fn quads_for_object(&self, object: EncodedTerm) -> DecodingQuadIterator {
self.ospg_quads(encode_term(object))
}
fn quads_for_graph(&self, graph_name: EncodedTerm) -> DecodingQuadIterator {
self.gspo_quads(encode_term(graph_name))
}
fn quads_for_subject_graph(
&self,
subject: EncodedTerm,
graph_name: EncodedTerm,
) -> DecodingQuadIterator {
self.gspo_quads(encode_term_pair(graph_name, subject))
}
fn quads_for_subject_predicate_graph(
&self,
subject: EncodedTerm,
predicate: EncodedTerm,
graph_name: EncodedTerm,
) -> DecodingQuadIterator {
self.gspo_quads(encode_term_triple(graph_name, subject, predicate))
}
fn quads_for_subject_object_graph(
&self,
subject: EncodedTerm,
object: EncodedTerm,
graph_name: EncodedTerm,
) -> DecodingQuadIterator {
self.gosp_quads(encode_term_triple(graph_name, object, subject))
}
fn quads_for_predicate_graph(
&self,
predicate: EncodedTerm,
graph_name: EncodedTerm,
) -> DecodingQuadIterator {
self.gpos_quads(encode_term_pair(graph_name, predicate))
}
fn quads_for_predicate_object_graph(
&self,
predicate: EncodedTerm,
object: EncodedTerm,
graph_name: EncodedTerm,
) -> DecodingQuadIterator {
self.gpos_quads(encode_term_triple(graph_name, predicate, object))
}
fn quads_for_object_graph(
&self,
object: EncodedTerm,
graph_name: EncodedTerm,
) -> DecodingQuadIterator {
self.gosp_quads(encode_term_pair(graph_name, object))
}
fn spog_quads(&self, prefix: Vec<u8>) -> DecodingQuadIterator {
self.inner_quads(&self.spog, prefix, QuadEncoding::SPOG)
}
fn posg_quads(&self, prefix: Vec<u8>) -> DecodingQuadIterator {
self.inner_quads(&self.posg, prefix, QuadEncoding::POSG)
}
fn ospg_quads(&self, prefix: Vec<u8>) -> DecodingQuadIterator {
self.inner_quads(&self.ospg, prefix, QuadEncoding::OSPG)
}
fn gspo_quads(&self, prefix: Vec<u8>) -> DecodingQuadIterator {
self.inner_quads(&self.gspo, prefix, QuadEncoding::GSPO)
}
fn gpos_quads(&self, prefix: Vec<u8>) -> DecodingQuadIterator {
self.inner_quads(&self.gpos, prefix, QuadEncoding::GPOS)
}
fn gosp_quads(&self, prefix: Vec<u8>) -> DecodingQuadIterator {
self.inner_quads(&self.gosp, prefix, QuadEncoding::GOSP)
}
fn inner_quads(
&self,
tree: &Tree,
prefix: Vec<u8>,
order: QuadEncoding,
) -> DecodingQuadIterator {
DecodingQuadIterator {
iter: tree.scan_prefix(prefix),
order,
}
}
}
impl StrLookup for SledStore {
fn get_str(&self, id: StrHash) -> Result<Option<String>> {
Ok(self
.id2str
.get(id.to_be_bytes())?
.map(|v| String::from_utf8(v.to_vec()))
.transpose()?)
}
}
impl ReadableEncodedStore for SledStore {
fn encoded_quads_for_pattern<'a>(
&'a self,
subject: Option<EncodedTerm>,
predicate: Option<EncodedTerm>,
object: Option<EncodedTerm>,
graph_name: Option<EncodedTerm>,
) -> Box<dyn Iterator<Item = Result<EncodedQuad>> + 'a> {
Box::new(self.encoded_quads_for_pattern_inner(subject, predicate, object, graph_name))
}
}
impl<'a> StrContainer for &'a SledStore {
fn insert_str(&mut self, key: StrHash, value: &str) -> Result<()> {
self.id2str.insert(key.to_be_bytes(), value)?;
Ok(())
}
}
impl<'a> WritableEncodedStore for &'a SledStore {
fn insert_encoded(&mut self, quad: &EncodedQuad) -> Result<()> {
//TODO: atomicity
let mut buffer = Vec::with_capacity(4 * WRITTEN_TERM_MAX_SIZE);
write_spog_quad(&mut buffer, quad);
self.spog.insert(&buffer, &[])?;
buffer.clear();
write_posg_quad(&mut buffer, quad);
self.posg.insert(&buffer, &[])?;
buffer.clear();
write_ospg_quad(&mut buffer, quad);
self.ospg.insert(&buffer, &[])?;
buffer.clear();
write_gspo_quad(&mut buffer, quad);
self.gspo.insert(&buffer, &[])?;
buffer.clear();
write_gpos_quad(&mut buffer, quad);
self.gpos.insert(&buffer, &[])?;
buffer.clear();
write_gosp_quad(&mut buffer, quad);
self.gosp.insert(&buffer, &[])?;
buffer.clear();
Ok(())
}
fn remove_encoded(&mut self, quad: &EncodedQuad) -> Result<()> {
//TODO: atomicity
let mut buffer = Vec::with_capacity(4 * WRITTEN_TERM_MAX_SIZE);
write_spog_quad(&mut buffer, quad);
self.spog.remove(&buffer)?;
buffer.clear();
write_posg_quad(&mut buffer, quad);
self.posg.remove(&buffer)?;
buffer.clear();
write_ospg_quad(&mut buffer, quad);
self.ospg.remove(&buffer)?;
buffer.clear();
write_gspo_quad(&mut buffer, quad);
self.gspo.remove(&buffer)?;
buffer.clear();
write_gpos_quad(&mut buffer, quad);
self.gpos.remove(&buffer)?;
buffer.clear();
write_gosp_quad(&mut buffer, quad);
self.gosp.remove(&buffer)?;
buffer.clear();
Ok(())
}
}
fn encode_term(t: EncodedTerm) -> Vec<u8> {
let mut vec = Vec::with_capacity(WRITTEN_TERM_MAX_SIZE);
write_term(&mut vec, t);
vec
}
fn encode_term_pair(t1: EncodedTerm, t2: EncodedTerm) -> Vec<u8> {
let mut vec = Vec::with_capacity(2 * WRITTEN_TERM_MAX_SIZE);
write_term(&mut vec, t1);
write_term(&mut vec, t2);
vec
}
fn encode_term_triple(t1: EncodedTerm, t2: EncodedTerm, t3: EncodedTerm) -> Vec<u8> {
let mut vec = Vec::with_capacity(3 * WRITTEN_TERM_MAX_SIZE);
write_term(&mut vec, t1);
write_term(&mut vec, t2);
write_term(&mut vec, t3);
vec
}
fn encode_term_quad(t1: EncodedTerm, t2: EncodedTerm, t3: EncodedTerm, t4: EncodedTerm) -> Vec<u8> {
let mut vec = Vec::with_capacity(4 * WRITTEN_TERM_MAX_SIZE);
write_term(&mut vec, t1);
write_term(&mut vec, t2);
write_term(&mut vec, t3);
write_term(&mut vec, t4);
vec
}
struct DecodingQuadIterator {
iter: Iter,
order: QuadEncoding,
}
impl Iterator for DecodingQuadIterator {
type Item = Result<EncodedQuad>;
fn next(&mut self) -> Option<Result<EncodedQuad>> {
Some(match self.iter.next()? {
Ok((encoded, _)) => self.order.decode(&encoded),
Err(error) => Err(error.into()),
})
}
}
#[test]
fn store() -> Result<()> {
use crate::model::*;
use crate::*;
let main_s = NamedOrBlankNode::from(BlankNode::default());
let main_p = NamedNode::parse("http://example.com")?;
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(None))
.collect::<Result<Vec<_>>>()?,
target
);
assert_eq!(
store
.quads_for_pattern(Some(&main_s), Some(&main_p), None, Some(None))
.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(None))
.collect::<Result<Vec<_>>>()?,
target
);
assert_eq!(
store
.quads_for_pattern(Some(&main_s), None, None, Some(None))
.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(None))
.collect::<Result<Vec<_>>>()?,
all_o
);
assert_eq!(
store
.quads_for_pattern(None, Some(&main_p), Some(&main_o), Some(None))
.collect::<Result<Vec<_>>>()?,
target
);
Ok(())
}
Loading…
Cancel
Save