diff --git a/js/src/store.rs b/js/src/store.rs index ac7d60e9..86748841 100644 --- a/js/src/store.rs +++ b/js/src/store.rs @@ -4,7 +4,7 @@ use crate::utils::to_err; use js_sys::{Array, Map}; use oxigraph::model::NamedOrBlankNode; use oxigraph::sparql::{QueryOptions, QueryResult}; -use oxigraph::{DatasetSyntax, Error, FileSyntax, GraphSyntax, MemoryStore}; +use oxigraph::{DatasetSyntax, FileSyntax, GraphSyntax, MemoryStore}; use std::convert::TryInto; use std::io::Cursor; use wasm_bindgen::prelude::*; @@ -37,15 +37,13 @@ impl JsMemoryStore { } pub fn add(&self, quad: &JsValue) -> Result<(), JsValue> { - self.store - .insert(&self.from_js.to_quad(quad)?.try_into()?) - .map_err(to_err) + self.store.insert(self.from_js.to_quad(quad)?.try_into()?); + Ok(()) } pub fn delete(&self, quad: &JsValue) -> Result<(), JsValue> { - self.store - .remove(&self.from_js.to_quad(quad)?.try_into()?) - .map_err(to_err) + self.store.remove(&self.from_js.to_quad(quad)?.try_into()?); + Ok(()) } pub fn has(&self, quad: &JsValue) -> Result { @@ -106,7 +104,7 @@ impl JsMemoryStore { } None => None, }.as_ref().map(|v| v.as_ref()), - ).map(|v| v.map(|v| JsQuad::from(v).into())).collect::,Error>>().map_err(to_err)?.into_boxed_slice()) + ).map(|v| JsQuad::from(v).into()).collect::>().into_boxed_slice()) } pub fn query(&self, query: &str) -> Result { diff --git a/lib/benches/sparql_query.rs b/lib/benches/sparql_query.rs index 82f751c7..51d81c78 100644 --- a/lib/benches/sparql_query.rs +++ b/lib/benches/sparql_query.rs @@ -42,6 +42,26 @@ fn sparql_w3c_syntax_bench(c: &mut Criterion) { }); } +fn load_graph_to_store( + url: &str, + store: &MemoryStore, + to_graph_name: Option<&NamedOrBlankNode>, +) -> Result<()> { + let syntax = if url.ends_with(".nt") { + GraphSyntax::NTriples + } else if url.ends_with(".ttl") { + GraphSyntax::Turtle + } else if url.ends_with(".rdf") { + GraphSyntax::RdfXml + } else { + return Err(Error::msg(format!( + "Serialization type not found for {}", + url + ))); + }; + store.load_graph(read_file(url)?, syntax, to_graph_name, Some(url)) +} + fn to_relative_path(url: &str) -> Result { if url.starts_with("http://www.w3.org/2001/sw/DataAccess/tests/data-r2/") { Ok(url.replace( @@ -78,33 +98,37 @@ fn read_file_to_string(url: &str) -> Result { Ok(string) } -fn load_graph(url: &str) -> Result { - let store = MemoryStore::default(); - load_graph_to_store(url, &store, None)?; - Ok(store - .quads_for_pattern(None, None, None, Some(None)) - .map(|q| q.unwrap().into_triple()) - .collect()) -} +mod rs { + use lazy_static::lazy_static; + use oxigraph::model::NamedNode; -fn load_graph_to_store( - url: &str, - store: &MemoryStore, - to_graph_name: Option<&NamedOrBlankNode>, -) -> Result<()> { - let syntax = if url.ends_with(".nt") { - GraphSyntax::NTriples - } else if url.ends_with(".ttl") { - GraphSyntax::Turtle - } else if url.ends_with(".rdf") { - GraphSyntax::RdfXml - } else { - return Err(Error::msg(format!( - "Serialization type not found for {}", - url - ))); - }; - store.load_graph(read_file(url)?, syntax, to_graph_name, Some(url)) + lazy_static! { + pub static ref RESULT_SET: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#ResultSet") + .unwrap(); + pub static ref RESULT_VARIABLE: NamedNode = NamedNode::parse( + "http://www.w3.org/2001/sw/DataAccess/tests/result-set#resultVariable" + ) + .unwrap(); + pub static ref SOLUTION: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#solution") + .unwrap(); + pub static ref BINDING: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#binding") + .unwrap(); + pub static ref VALUE: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#value") + .unwrap(); + pub static ref VARIABLE: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#variable") + .unwrap(); + pub static ref INDEX: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#index") + .unwrap(); + pub static ref BOOLEAN: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#boolean") + .unwrap(); + } } mod mf { @@ -118,6 +142,9 @@ mod mf { pub static ref ENTRIES: NamedNode = NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#entries") .unwrap(); + pub static ref NAME: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#name") + .unwrap(); pub static ref ACTION: NamedNode = NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#action") .unwrap(); @@ -135,38 +162,16 @@ mod qt { pub static ref QUERY: NamedNode = NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#query") .unwrap(); - } -} - -mod rs { - use lazy_static::lazy_static; - use oxigraph::model::NamedNode; - - lazy_static! { - pub static ref RESULT_SET: NamedNode = - NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#ResultSet") + pub static ref DATA: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#data").unwrap(); + pub static ref GRAPH_DATA: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#graphData") .unwrap(); - pub static ref RESULT_VARIABLE: NamedNode = NamedNode::parse( - "http://www.w3.org/2001/sw/DataAccess/tests/result-set#resultVariable" - ) - .unwrap(); - pub static ref SOLUTION: NamedNode = - NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#solution") - .unwrap(); - pub static ref BINDING: NamedNode = - NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#binding") - .unwrap(); - pub static ref VALUE: NamedNode = - NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#value") + pub static ref SERVICE_DATA: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#serviceData") .unwrap(); - pub static ref VARIABLE: NamedNode = - NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#variable") - .unwrap(); - pub static ref INDEX: NamedNode = - NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#index") - .unwrap(); - pub static ref BOOLEAN: NamedNode = - NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#boolean") + pub static ref ENDPOINT: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#endpoint") .unwrap(); } } @@ -177,7 +182,7 @@ struct Test { } struct TestManifest { - graph: SimpleGraph, + graph: MemoryStore, tests_to_do: Vec, manifests_to_do: Vec, } @@ -185,7 +190,7 @@ struct TestManifest { impl TestManifest { fn new(url: impl Into) -> TestManifest { Self { - graph: SimpleGraph::default(), + graph: MemoryStore::new(), tests_to_do: Vec::default(), manifests_to_do: vec![url.into()], } @@ -198,38 +203,34 @@ impl Iterator for TestManifest { fn next(&mut self) -> Option> { match self.tests_to_do.pop() { Some(Term::NamedNode(test_node)) => { - let test_subject = NamedOrBlankNode::from(test_node); - let kind = match self - .graph - .object_for_subject_predicate(&test_subject, &rdf::TYPE) - { - Some(Term::NamedNode(c)) => match c.as_str().split('#').last() { - Some(k) => k.to_string(), - None => return self.next(), //We ignore the test - }, - _ => return self.next(), //We ignore the test - }; - let query = match self - .graph - .object_for_subject_predicate(&test_subject, &*mf::ACTION) - { - Some(Term::NamedNode(n)) => n.as_str().to_owned(), - Some(Term::BlankNode(n)) => { - let n = n.clone().into(); - match self.graph.object_for_subject_predicate(&n, &qt::QUERY) { - Some(Term::NamedNode(q)) => q.as_str().to_owned(), - Some(_) => return Some(Err(Error::msg("invalid query"))), - None => return Some(Err(Error::msg("query not found"))), + let test_subject = NamedOrBlankNode::from(test_node.clone()); + let kind = + match object_for_subject_predicate(&self.graph, &test_subject, &rdf::TYPE) { + Some(Term::NamedNode(c)) => match c.as_str().split('#').last() { + Some(k) => k.to_string(), + None => return self.next(), //We ignore the test + }, + _ => return self.next(), //We ignore the test + }; + let query = + match object_for_subject_predicate(&self.graph, &test_subject, &*mf::ACTION) { + Some(Term::NamedNode(n)) => n.as_str().to_owned(), + Some(Term::BlankNode(n)) => { + let n = n.clone().into(); + match object_for_subject_predicate(&self.graph, &n, &qt::QUERY) { + Some(Term::NamedNode(q)) => q.as_str().to_owned(), + Some(_) => return Some(Err(Error::msg("invalid query"))), + None => return Some(Err(Error::msg("query not found"))), + } } - } - Some(_) => return Some(Err(Error::msg("invalid action"))), - None => { - return Some(Err(Error::msg(format!( - "action not found for test {}", - test_subject - )))); - } - }; + Some(_) => return Some(Err(Error::msg("invalid action"))), + None => { + return Some(Err(Error::msg(format!( + "action not found for test {}", + test_subject + )))); + } + }; Some(Ok(Test { kind, query })) } Some(_) => Some(Err(Error::msg("invalid test list"))), @@ -238,16 +239,11 @@ impl Iterator for TestManifest { Some(url) => { let manifest = NamedOrBlankNode::from(NamedNode::parse(url.clone()).unwrap()); - match load_graph(&url) { - Ok(g) => self.graph.extend(g.into_iter()), - Err(e) => return Some(Err(e)), + if let Err(e) = load_graph_to_store(&url, &self.graph, None) { + return Some(Err(e)); } - // New manifests - match self - .graph - .object_for_subject_predicate(&manifest, &*mf::INCLUDE) - { + match object_for_subject_predicate(&self.graph, &manifest, &*mf::INCLUDE) { Some(Term::BlankNode(list)) => { self.manifests_to_do.extend( RdfListIterator::iter(&self.graph, list.clone().into()) @@ -262,10 +258,7 @@ impl Iterator for TestManifest { } // New tests - match self - .graph - .object_for_subject_predicate(&manifest, &*mf::ENTRIES) - { + match object_for_subject_predicate(&self.graph, &manifest, &*mf::ENTRIES) { Some(Term::BlankNode(list)) => { self.tests_to_do.extend(RdfListIterator::iter( &self.graph, @@ -290,12 +283,12 @@ impl Iterator for TestManifest { } struct RdfListIterator<'a> { - graph: &'a SimpleGraph, + graph: &'a MemoryStore, current_node: Option, } impl<'a> RdfListIterator<'a> { - fn iter(graph: &'a SimpleGraph, root: NamedOrBlankNode) -> RdfListIterator<'a> { + fn iter(graph: &'a MemoryStore, root: NamedOrBlankNode) -> RdfListIterator<'a> { RdfListIterator { graph, current_node: Some(root), @@ -309,21 +302,35 @@ impl<'a> Iterator for RdfListIterator<'a> { fn next(&mut self) -> Option { match self.current_node.clone() { Some(current) => { - let result = self - .graph - .object_for_subject_predicate(¤t, &rdf::FIRST); - self.current_node = match self - .graph - .object_for_subject_predicate(¤t, &rdf::REST) - { - Some(Term::NamedNode(ref n)) if *n == *rdf::NIL => None, - Some(Term::NamedNode(n)) => Some(n.clone().into()), - Some(Term::BlankNode(n)) => Some(n.clone().into()), - _ => None, - }; - result.cloned() + let result = object_for_subject_predicate(&self.graph, ¤t, &rdf::FIRST); + self.current_node = + match object_for_subject_predicate(&self.graph, ¤t, &rdf::REST) { + Some(Term::NamedNode(ref n)) if *n == *rdf::NIL => None, + Some(Term::NamedNode(n)) => Some(n.into()), + Some(Term::BlankNode(n)) => Some(n.into()), + _ => None, + }; + result } None => None, } } } + +fn object_for_subject_predicate( + store: &MemoryStore, + subject: &NamedOrBlankNode, + predicate: &NamedNode, +) -> Option { + objects_for_subject_predicate(store, subject, predicate).next() +} + +fn objects_for_subject_predicate( + store: &MemoryStore, + subject: &NamedOrBlankNode, + predicate: &NamedNode, +) -> impl Iterator { + store + .quads_for_pattern(Some(subject), Some(predicate), None, None) + .map(|t| t.object_owned()) +} diff --git a/lib/benches/store.rs b/lib/benches/store.rs index 7a08d819..a8a82701 100644 --- a/lib/benches/store.rs +++ b/lib/benches/store.rs @@ -25,7 +25,7 @@ fn memory_load_bench(c: &mut Criterion) { b.iter(|| { let store = MemoryStore::new(); for quad in &quads { - store.insert(quad).unwrap(); + store.insert(quad.clone()); } }); }); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index e77e93eb..7f3f0d34 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -20,16 +20,16 @@ //! use crate::oxigraph::sparql::QueryOptions; //! use oxigraph::sparql::QueryResult; //! -//! let store = MemoryStore::default(); +//! let store = MemoryStore::new(); //! //! // insertion //! let ex = NamedNode::parse("http://example.com")?; //! let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), None); -//! store.insert(&quad)?; +//! store.insert(quad.clone()); //! //! // quad filter -//! let results: Result> = store.quads_for_pattern(None, None, None, None).collect(); -//! assert_eq!(vec![quad], results?); +//! let results: Vec = store.quads_for_pattern(Some(&ex.clone().into()), 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())?; diff --git a/lib/src/model/graph.rs b/lib/src/model/graph.rs deleted file mode 100644 index 71413e80..00000000 --- a/lib/src/model/graph.rs +++ /dev/null @@ -1,178 +0,0 @@ -use crate::model::isomorphism::are_graphs_isomorphic; -use crate::model::*; -use std::collections::HashSet; -use std::fmt; -use std::iter::FromIterator; - -/// A simple implementation of [RDF graphs](https://www.w3.org/TR/rdf11-concepts/#dfn-graph). -/// -/// It is not done to hold big graphs. -/// -/// Usage example: -/// ``` -/// use oxigraph::model::*; -/// use oxigraph::model::SimpleGraph; -/// -/// let mut graph = SimpleGraph::default(); -/// let ex = NamedNode::parse("http://example.com").unwrap(); -/// let triple = Triple::new(ex.clone(), ex.clone(), ex.clone()); -/// graph.insert(triple.clone()); -/// let results: Vec = graph.triples_for_subject(&ex.into()).cloned().collect(); -/// assert_eq!(vec![triple], results); -/// ``` -#[derive(Eq, PartialEq, Debug, Clone, Default)] -pub struct SimpleGraph { - triples: HashSet, -} - -impl SimpleGraph { - /// Returns all triples contained by the graph - pub fn iter(&self) -> impl Iterator { - self.triples.iter() - } - - pub fn triples_for_subject<'a>( - &'a self, - subject: &'a NamedOrBlankNode, - ) -> impl Iterator + 'a { - self.iter().filter(move |t| t.subject() == subject) - } - - pub fn objects_for_subject_predicate<'a>( - &'a self, - subject: &'a NamedOrBlankNode, - predicate: &'a NamedNode, - ) -> impl Iterator + 'a { - self.iter().filter_map(move |t| { - if t.subject() == subject && t.predicate() == predicate { - Some(t.object()) - } else { - None - } - }) - } - - pub fn object_for_subject_predicate<'a>( - &'a self, - subject: &'a NamedOrBlankNode, - predicate: &'a NamedNode, - ) -> Option<&'a Term> { - self.objects_for_subject_predicate(subject, predicate) - .next() - } - - pub fn predicates_for_subject_object<'a>( - &'a self, - subject: &'a NamedOrBlankNode, - object: &'a Term, - ) -> impl Iterator + 'a { - self.iter().filter_map(move |t| { - if t.subject() == subject && t.object() == object { - Some(t.predicate()) - } else { - None - } - }) - } - - pub fn triples_for_predicate<'a>( - &'a self, - predicate: &'a NamedNode, - ) -> impl Iterator + 'a { - self.iter().filter(move |t| t.predicate() == predicate) - } - - pub fn subjects_for_predicate_object<'a>( - &'a self, - predicate: &'a NamedNode, - object: &'a Term, - ) -> impl Iterator + 'a { - self.iter().filter_map(move |t| { - if t.predicate() == predicate && t.object() == object { - Some(t.subject()) - } else { - None - } - }) - } - - pub fn triples_for_object<'a>( - &'a self, - object: &'a Term, - ) -> impl Iterator + 'a { - self.iter().filter(move |t| t.object() == object) - } - - /// Checks if the graph contains the given triple - pub fn contains(&self, triple: &Triple) -> bool { - self.triples.contains(triple) - } - - /// Adds a triple to the graph - pub fn insert(&mut self, triple: Triple) -> bool { - self.triples.insert(triple) - } - - /// Removes a concrete triple from the graph - pub fn remove(&mut self, triple: &Triple) -> bool { - self.triples.remove(triple) - } - - /// Returns the number of triples in this graph - pub fn len(&self) -> usize { - self.triples.len() - } - - /// Checks if this graph contains a triple - pub fn is_empty(&self) -> bool { - self.triples.is_empty() - } - - /// Checks if the current graph is [isomorphic](https://www.w3.org/TR/rdf11-concepts/#dfn-graph-isomorphism) with another one - /// - /// Warning: This algorithm worst case complexity is in O(n!) - pub fn is_isomorphic(&self, other: &SimpleGraph) -> bool { - are_graphs_isomorphic(self, other) - } -} - -impl IntoIterator for SimpleGraph { - type Item = Triple; - type IntoIter = as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.triples.into_iter() - } -} - -impl<'a> IntoIterator for &'a SimpleGraph { - type Item = &'a Triple; - type IntoIter = <&'a HashSet as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.triples.iter() - } -} - -impl FromIterator for SimpleGraph { - fn from_iter>(iter: I) -> Self { - Self { - triples: HashSet::from_iter(iter), - } - } -} - -impl Extend for SimpleGraph { - fn extend>(&mut self, iter: I) { - self.triples.extend(iter) - } -} - -impl fmt::Display for SimpleGraph { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for t in &self.triples { - writeln!(f, "{}", t)?; - } - Ok(()) - } -} diff --git a/lib/src/model/isomorphism.rs b/lib/src/model/isomorphism.rs deleted file mode 100644 index 63d79e01..00000000 --- a/lib/src/model/isomorphism.rs +++ /dev/null @@ -1,289 +0,0 @@ -use crate::model::*; -use std::collections::hash_map::{DefaultHasher, RandomState}; -use std::collections::{BTreeSet, HashMap, HashSet}; -use std::hash::Hash; -use std::hash::Hasher; - -#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] -struct SubjectPredicate<'a> { - subject: &'a NamedOrBlankNode, - predicate: &'a NamedNode, -} - -#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] -struct PredicateObject<'a> { - predicate: &'a NamedNode, - object: &'a Term, -} - -fn subject_predicates_for_object<'a>( - graph: &'a SimpleGraph, - object: &'a Term, -) -> impl Iterator> + 'a { - graph.triples_for_object(object).map(|t| SubjectPredicate { - subject: t.subject(), - predicate: t.predicate(), - }) -} - -fn predicate_objects_for_subject<'a>( - graph: &'a SimpleGraph, - subject: &'a NamedOrBlankNode, -) -> impl Iterator> + 'a { - graph.triples_for_subject(subject).map(|t| PredicateObject { - predicate: t.predicate(), - object: t.object(), - }) -} - -fn split_hash_buckets<'a>( - bnodes_by_hash: HashMap>, - graph: &'a SimpleGraph, - distance: usize, -) -> HashMap> { - let mut new_bnodes_by_hash = HashMap::default(); - - for (hash, bnodes) in bnodes_by_hash { - if bnodes.len() == 1 { - new_bnodes_by_hash.insert(hash, bnodes); // Nothing to improve - } else { - for bnode in bnodes { - let mut starts = vec![NamedOrBlankNode::from(*bnode)]; - for _ in 0..distance { - let mut new_starts = Vec::default(); - for s in starts { - for t in graph.triples_for_subject(&s) { - match t.object() { - Term::NamedNode(t) => new_starts.push(t.clone().into()), - Term::BlankNode(t) => new_starts.push(t.clone().into()), - Term::Literal(_) => (), - } - } - for t in graph.triples_for_object(&s.into()) { - new_starts.push(t.subject().clone()); - } - } - starts = new_starts; - } - - // We do the hashing - let mut hasher = DefaultHasher::default(); - hash.hash(&mut hasher); // We start with the previous hash - - // NB: we need to sort the triples to have the same hash - let mut po_set: BTreeSet> = BTreeSet::default(); - for start in &starts { - for po in predicate_objects_for_subject(graph, start) { - match &po.object { - Term::BlankNode(_) => (), - _ => { - po_set.insert(po); - } - } - } - } - for po in &po_set { - po.hash(&mut hasher); - } - - let mut sp_set: BTreeSet> = BTreeSet::default(); - let term_starts: Vec<_> = starts.into_iter().map(|t| t.into()).collect(); - for start in &term_starts { - for sp in subject_predicates_for_object(graph, start) { - match &sp.subject { - NamedOrBlankNode::BlankNode(_) => (), - _ => { - sp_set.insert(sp); - } - } - } - } - for sp in &sp_set { - sp.hash(&mut hasher); - } - - new_bnodes_by_hash - .entry(hasher.finish()) - .or_insert_with(Vec::default) - .push(bnode); - } - } - } - new_bnodes_by_hash -} - -fn build_and_check_containment_from_hashes<'a>( - a_bnodes_by_hash: &mut Vec<(u64, Vec<&'a BlankNode>)>, - b_bnodes_by_hash: &'a HashMap>, - a_to_b_mapping: &mut HashMap<&'a BlankNode, &'a BlankNode>, - a: &'a SimpleGraph, - b: &'a SimpleGraph, - current_a_nodes: &[&'a BlankNode], - current_b_nodes: &mut BTreeSet<&'a BlankNode>, -) -> bool { - if let Some((a_node, remaining_a_node)) = current_a_nodes.split_last() { - let b_nodes = current_b_nodes.iter().cloned().collect::>(); - for b_node in b_nodes { - current_b_nodes.remove(b_node); - a_to_b_mapping.insert(a_node, b_node); - if check_is_contained_focused(a_to_b_mapping, a_node, a, b) - && build_and_check_containment_from_hashes( - a_bnodes_by_hash, - b_bnodes_by_hash, - a_to_b_mapping, - a, - b, - remaining_a_node, - current_b_nodes, - ) - { - return true; - } - current_b_nodes.insert(b_node); - } - a_to_b_mapping.remove(a_node); - false - } else { - let (hash, new_a_nodes) = match a_bnodes_by_hash.pop() { - Some(v) => v, - None => return true, - }; - - let mut new_b_nodes = b_bnodes_by_hash - .get(&hash) - .map_or(BTreeSet::default(), |v| v.iter().cloned().collect()); - if new_a_nodes.len() != new_b_nodes.len() { - return false; - } - - if new_a_nodes.len() > 10 { - eprintln!("Too big instance, aborting"); - return true; //TODO: Very very very bad - } - - if build_and_check_containment_from_hashes( - a_bnodes_by_hash, - b_bnodes_by_hash, - a_to_b_mapping, - a, - b, - &new_a_nodes, - &mut new_b_nodes, - ) { - true - } else { - a_bnodes_by_hash.push((hash, new_a_nodes)); - false - } - } -} - -fn check_is_contained_focused<'a>( - a_to_b_mapping: &mut HashMap<&'a BlankNode, &'a BlankNode>, - a_bnode_focus: &'a BlankNode, - a: &'a SimpleGraph, - b: &'a SimpleGraph, -) -> bool { - let a_bnode_subject = a_bnode_focus.clone().into(); - let a_bnode_object = a_bnode_focus.clone().into(); - let ts_a = a - .triples_for_subject(&a_bnode_subject) - .chain(a.triples_for_object(&a_bnode_object)); - for t_a in ts_a { - let subject: NamedOrBlankNode = if let NamedOrBlankNode::BlankNode(s_a) = &t_a.subject() { - if let Some(s_a) = a_to_b_mapping.get(s_a) { - (*s_a).clone().into() - } else { - continue; // We skip for now - } - } else { - t_a.subject().clone() - }; - let predicate = t_a.predicate().clone(); - let object: Term = if let Term::BlankNode(o_a) = &t_a.object() { - if let Some(o_a) = a_to_b_mapping.get(o_a) { - (*o_a).clone().into() - } else { - continue; // We skip for now - } - } else { - t_a.object().clone() - }; - if !b.contains(&Triple::new(subject, predicate, object)) { - return false; - } - } - - true -} - -fn graph_blank_nodes(graph: &SimpleGraph) -> Vec<&BlankNode> { - let mut blank_nodes: HashSet<&BlankNode, RandomState> = HashSet::default(); - for t in graph { - if let NamedOrBlankNode::BlankNode(subject) = t.subject() { - blank_nodes.insert(subject); - } - if let Term::BlankNode(object) = &t.object() { - blank_nodes.insert(object); - } - } - blank_nodes.into_iter().collect() -} - -pub fn are_graphs_isomorphic(a: &SimpleGraph, b: &SimpleGraph) -> bool { - if a.len() != b.len() { - return false; - } - - // We check containment of everything buts triples with blank nodes - let mut a_bnodes_triples = SimpleGraph::default(); - for t in a { - if t.subject().is_blank_node() || t.object().is_blank_node() { - a_bnodes_triples.insert(t.clone()); - } else if !b.contains(t) { - return false; // Triple in a not in b without blank nodes - } - } - - let mut b_bnodes_triples = SimpleGraph::default(); - for t in b { - if t.subject().is_blank_node() || t.object().is_blank_node() { - b_bnodes_triples.insert(t.clone()); - } else if !a.contains(t) { - return false; // Triple in a not in b without blank nodes - } - } - - let mut a_bnodes_by_hash = HashMap::default(); - a_bnodes_by_hash.insert(0, graph_blank_nodes(&a_bnodes_triples)); - let mut b_bnodes_by_hash = HashMap::default(); - b_bnodes_by_hash.insert(0, graph_blank_nodes(&b_bnodes_triples)); - - for distance in 0..5 { - let max_size = a_bnodes_by_hash.values().map(Vec::len).max().unwrap_or(0); - if max_size < 2 { - break; // We only have small buckets - } - - a_bnodes_by_hash = split_hash_buckets(a_bnodes_by_hash, a, distance); - b_bnodes_by_hash = split_hash_buckets(b_bnodes_by_hash, b, distance); - - // Hashes should have the same size - if a_bnodes_by_hash.len() != b_bnodes_by_hash.len() { - return false; - } - } - - let mut sorted_a_bnodes_by_hash: Vec<_> = a_bnodes_by_hash.into_iter().collect(); - sorted_a_bnodes_by_hash.sort_by(|(_, l1), (_, l2)| l1.len().cmp(&l2.len())); - - build_and_check_containment_from_hashes( - &mut sorted_a_bnodes_by_hash, - &b_bnodes_by_hash, - &mut HashMap::default(), - &a_bnodes_triples, - &b_bnodes_triples, - &[], - &mut BTreeSet::default(), - ) -} diff --git a/lib/src/model/mod.rs b/lib/src/model/mod.rs index 50dffee1..a13e10fc 100644 --- a/lib/src/model/mod.rs +++ b/lib/src/model/mod.rs @@ -3,8 +3,6 @@ //! Inspired by [RDF/JS](https://rdf.js.org/data-model-spec/) and [Apache Commons RDF](http://commons.apache.org/proper/commons-rdf/) mod blank_node; -mod graph; -mod isomorphism; mod literal; mod named_node; mod triple; @@ -12,7 +10,6 @@ pub mod vocab; pub(crate) mod xsd; pub use crate::model::blank_node::BlankNode; -pub use crate::model::graph::SimpleGraph; pub use crate::model::literal::Literal; pub use crate::model::named_node::NamedNode; pub use crate::model::triple::NamedOrBlankNode; diff --git a/lib/src/store/memory.rs b/lib/src/store/memory.rs index da99b77a..c2e99f42 100644 --- a/lib/src/store/memory.rs +++ b/lib/src/store/memory.rs @@ -5,9 +5,13 @@ use crate::sparql::{QueryOptions, QueryResult, SimplePreparedQuery}; use crate::store::numeric_encoder::*; use crate::store::*; use crate::{DatasetSyntax, GraphSyntax, Result}; -use std::collections::{HashMap, HashSet}; +use std::collections::hash_map::DefaultHasher; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::fmt; use std::hash::Hash; +use std::hash::Hasher; use std::io::BufRead; +use std::iter::FromIterator; use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; /// In-memory store. @@ -25,11 +29,11 @@ use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; /// // insertion /// let ex = NamedNode::parse("http://example.com")?; /// let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), None); -/// store.insert(&quad)?; +/// store.insert(quad.clone()); /// /// // quad filter -/// let results: Result> = store.quads_for_pattern(None, None, None, None).collect(); -/// assert_eq!(vec![quad], results?); +/// let results: Vec = store.quads_for_pattern(Some(&ex.clone().into()), 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())?; @@ -81,11 +85,11 @@ impl MemoryStore { /// use oxigraph::{MemoryStore, Result}; /// use oxigraph::sparql::{QueryOptions, QueryResult}; /// - /// let store = MemoryStore::default(); + /// let store = MemoryStore::new(); /// /// // insertions /// let ex = NamedNode::parse("http://example.com")?; - /// store.insert(&Quad::new(ex.clone(), ex.clone(), ex.clone(), None)); + /// store.insert(Quad::new(ex.clone(), ex.clone(), ex.clone(), None)); /// /// // SPARQL query /// let prepared_query = store.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default())?; @@ -126,16 +130,16 @@ impl MemoryStore { /// use oxigraph::model::*; /// use oxigraph::{MemoryStore, Result}; /// - /// let store = MemoryStore::default(); + /// let store = MemoryStore::new(); /// /// // insertion /// let ex = NamedNode::parse("http://example.com")?; /// let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), None); - /// store.insert(&quad); + /// store.insert(quad.clone()); /// /// // quad filter - /// let results: Result> = store.quads_for_pattern(None, None, None, None).collect(); - /// assert_eq!(vec![quad], results?); + /// let results: Vec = store.quads_for_pattern(None, None, None, None).collect(); + /// assert_eq!(vec![quad], results); /// # Result::Ok(()) /// ``` #[allow(clippy::option_option)] @@ -145,7 +149,7 @@ impl MemoryStore { predicate: Option<&NamedNode>, object: Option<&Term>, graph_name: Option>, - ) -> impl Iterator> { + ) -> impl Iterator { let subject = subject.map(|s| s.into()); let predicate = predicate.map(|p| p.into()); let object = object.map(|o| o.into()); @@ -153,7 +157,9 @@ impl MemoryStore { let this = self.clone(); self.encoded_quads_for_pattern_inner(subject, predicate, object, graph_name) .into_iter() - .map(move |quad| this.decode_quad(&quad)) + .map( + move |quad| this.decode_quad(&quad).unwrap(), // Could not fail + ) } /// Checks if this store contains a given quad @@ -172,14 +178,15 @@ impl MemoryStore { /// use oxigraph::model::*; /// use oxigraph::{MemoryStore, Result}; /// - /// let store = MemoryStore::default(); + /// let store = MemoryStore::new(); /// /// let ex = NamedNode::parse("http://example.com")?; /// let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), None); /// /// // transaction /// store.transaction(|transaction| { - /// transaction.insert(&quad) + /// transaction.insert(quad.clone()); + /// Ok(()) /// }); /// /// // quad filter @@ -206,16 +213,16 @@ impl MemoryStore { /// use oxigraph::model::*; /// use oxigraph::{MemoryStore, Result, GraphSyntax}; /// - /// let store = MemoryStore::default(); + /// let store = MemoryStore::new(); /// /// // insertion /// let file = b" ."; /// store.load_graph(file.as_ref(), GraphSyntax::NTriples, None, None); /// /// // quad filter - /// let results: Result> = store.quads_for_pattern(None, None, None, None).collect(); + /// let results: Vec = store.quads_for_pattern(None, None, None, None).collect(); /// let ex = NamedNode::parse("http://example.com")?; - /// assert_eq!(vec![Quad::new(ex.clone(), ex.clone(), ex.clone(), None)], results?); + /// assert_eq!(vec![Quad::new(ex.clone(), ex.clone(), ex.clone(), None)], results); /// # Result::Ok(()) /// ``` pub fn load_graph( @@ -236,16 +243,16 @@ impl MemoryStore { /// use oxigraph::model::*; /// use oxigraph::{MemoryStore, Result, DatasetSyntax}; /// - /// let store = MemoryStore::default(); + /// let store = MemoryStore::new(); /// /// // insertion /// let file = b" ."; /// store.load_dataset(file.as_ref(), DatasetSyntax::NQuads, None); /// /// // quad filter - /// let results: Result> = store.quads_for_pattern(None, None, None, None).collect(); + /// let results: Vec = store.quads_for_pattern(None, None, None, None).collect(); /// let ex = NamedNode::parse("http://example.com")?; - /// assert_eq!(vec![Quad::new(ex.clone(), ex.clone(), ex.clone(), Some(ex.into()))], results?); + /// assert_eq!(vec![Quad::new(ex.clone(), ex.clone(), ex.clone(), Some(ex.into()))], results); /// # Result::Ok(()) /// ``` pub fn load_dataset( @@ -259,17 +266,25 @@ impl MemoryStore { } /// Adds a quad to this store. - pub fn insert(&self, quad: &Quad) -> Result<()> { + #[allow(clippy::needless_pass_by_value)] + pub fn insert(&self, quad: Quad) { let mut store = self; - let quad = store.encode_quad(quad)?; - store.insert_encoded(&quad) + let quad = store.encode_quad(&quad).unwrap(); // Could never fail + store.insert_encoded(&quad).unwrap(); // Could never fail } /// Removes a quad from this store. - pub fn remove(&self, quad: &Quad) -> Result<()> { + pub fn remove(&self, quad: &Quad) { let mut store = self; let quad = quad.into(); - store.remove_encoded(&quad) + store.remove_encoded(&quad).unwrap(); // Could never fail + } + + /// Returns if the current dataset is [isomorphic](https://www.w3.org/TR/rdf11-concepts/#dfn-dataset-isomorphism) with another one. + /// + /// Warning: This implementation worst-case complexity is in O(n!) + pub fn is_isomorphic(&self, other: &Self) -> bool { + are_datasets_isomorphic(self, other) } fn indexes(&self) -> RwLockReadGuard<'_, MemoryStoreIndexes> { @@ -825,7 +840,7 @@ impl<'a> MemoryTransaction<'a> { /// use oxigraph::model::*; /// use oxigraph::{MemoryStore, Result, GraphSyntax}; /// - /// let store = MemoryStore::default(); + /// let store = MemoryStore::new(); /// /// // insertion /// let file = b" ."; @@ -834,9 +849,9 @@ impl<'a> MemoryTransaction<'a> { /// })?; /// /// // quad filter - /// let results: Result> = store.quads_for_pattern(None, None, None, None).collect(); + /// let results: Vec = store.quads_for_pattern(None, None, None, None).collect(); /// let ex = NamedNode::parse("http://example.com")?; - /// assert_eq!(vec![Quad::new(ex.clone(), ex.clone(), ex.clone(), None)], results?); + /// assert_eq!(vec![Quad::new(ex.clone(), ex.clone(), ex.clone(), None)], results); /// # Result::Ok(()) /// ``` pub fn load_graph( @@ -856,16 +871,16 @@ impl<'a> MemoryTransaction<'a> { /// use oxigraph::model::*; /// use oxigraph::{MemoryStore, Result, DatasetSyntax}; /// - /// let store = MemoryStore::default(); + /// let store = MemoryStore::new(); /// /// // insertion /// let file = b" ."; /// store.load_dataset(file.as_ref(), DatasetSyntax::NQuads, None); /// /// // quad filter - /// let results: Result> = store.quads_for_pattern(None, None, None, None).collect(); + /// let results: Vec = store.quads_for_pattern(None, None, None, None).collect(); /// let ex = NamedNode::parse("http://example.com")?; - /// assert_eq!(vec![Quad::new(ex.clone(), ex.clone(), ex.clone(), Some(ex.into()))], results?); + /// assert_eq!(vec![Quad::new(ex.clone(), ex.clone(), ex.clone(), Some(ex.into()))], results); /// # Result::Ok(()) /// ``` pub fn load_dataset( @@ -878,15 +893,16 @@ impl<'a> MemoryTransaction<'a> { } /// Adds a quad to this store during the transaction. - pub fn insert(&mut self, quad: &Quad) -> Result<()> { - let quad = self.encode_quad(quad)?; - self.insert_encoded(&quad) + #[allow(clippy::needless_pass_by_value)] + pub fn insert(&mut self, quad: Quad) { + let quad = self.encode_quad(&quad).unwrap(); // Could never fail + self.insert_encoded(&quad).unwrap(); // Could never fail } /// Removes a quad from this store during the transaction. - pub fn remove(&mut self, quad: &Quad) -> Result<()> { + pub fn remove(&mut self, quad: &Quad) { let quad = quad.into(); - self.remove_encoded(&quad) + self.remove_encoded(&quad).unwrap(); // Could never fail } fn commit(self) -> Result<()> { @@ -920,3 +936,285 @@ impl WritableEncodedStore for MemoryTransaction<'_> { Ok(()) } } + +impl FromIterator for MemoryStore { + fn from_iter>(iter: I) -> Self { + let mut store = MemoryStore::new(); + store.extend(iter); + store + } +} + +impl Extend for MemoryStore { + fn extend>(&mut self, iter: T) { + for quad in iter { + self.insert(quad); + } + } +} + +impl fmt::Display for MemoryStore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for t in self.quads_for_pattern(None, None, None, None) { + writeln!(f, "{}", t)?; + } + Ok(()) + } +} + +// Isomorphism implementation + +fn split_hash_buckets( + bnodes_by_hash: HashMap>, + graph: &MemoryStore, + distance: usize, +) -> HashMap> { + let mut new_bnodes_by_hash = HashMap::default(); + + for (hash, bnodes) in bnodes_by_hash { + if bnodes.len() == 1 { + new_bnodes_by_hash.insert(hash, bnodes); // Nothing to improve + } else { + for bnode in bnodes { + let mut starts = vec![bnode]; + for _ in 0..distance { + let mut new_starts = Vec::default(); + for s in starts { + for q in graph.encoded_quads_for_subject(s) { + if q.object.is_named_node() || q.object.is_blank_node() { + new_starts.push(q.object) + } + } + for t in graph.encoded_quads_for_object(s) { + new_starts.push(t.subject); + } + } + starts = new_starts; + } + + // We do the hashing + let mut hasher = DefaultHasher::default(); + hash.hash(&mut hasher); // We start with the previous hash + + // NB: we need to sort the triples to have the same hash + let mut po_set = BTreeSet::default(); + for start in &starts { + for quad in graph.encoded_quads_for_subject(*start) { + if !quad.object.is_blank_node() { + po_set.insert(encode_term_pair(quad.predicate, quad.object)); + } + } + } + for po in &po_set { + po.hash(&mut hasher); + } + + let mut sp_set = BTreeSet::default(); + for start in starts { + for quad in graph.encoded_quads_for_object(start) { + if !quad.subject.is_blank_node() { + sp_set.insert(encode_term_pair(quad.subject, quad.predicate)); + } + } + } + for sp in &sp_set { + sp.hash(&mut hasher); + } + + new_bnodes_by_hash + .entry(hasher.finish()) + .or_insert_with(Vec::default) + .push(bnode); + } + } + } + new_bnodes_by_hash +} + +fn encode_term_pair(t1: EncodedTerm, t2: EncodedTerm) -> Vec { + let mut vec = Vec::with_capacity(2 * WRITTEN_TERM_MAX_SIZE); + write_term(&mut vec, t1); + write_term(&mut vec, t2); + vec +} + +fn build_and_check_containment_from_hashes<'a>( + a_bnodes_by_hash: &mut Vec<(u64, Vec)>, + b_bnodes_by_hash: &'a HashMap>, + a_to_b_mapping: &mut HashMap, + a: &'a HashSet, + b: &'a HashSet, + current_a_nodes: &[EncodedTerm], + current_b_nodes: &mut HashSet, +) -> bool { + if let Some((a_node, remaining_a_node)) = current_a_nodes.split_last() { + let b_nodes = current_b_nodes.iter().cloned().collect::>(); + for b_node in b_nodes { + current_b_nodes.remove(&b_node); + a_to_b_mapping.insert(*a_node, b_node); + if check_is_contained_focused(a_to_b_mapping, *a_node, a, b) + && build_and_check_containment_from_hashes( + a_bnodes_by_hash, + b_bnodes_by_hash, + a_to_b_mapping, + a, + b, + remaining_a_node, + current_b_nodes, + ) + { + return true; + } + current_b_nodes.insert(b_node); + } + a_to_b_mapping.remove(a_node); + false + } else { + let (hash, new_a_nodes) = match a_bnodes_by_hash.pop() { + Some(v) => v, + None => return true, + }; + + let mut new_b_nodes = b_bnodes_by_hash + .get(&hash) + .map_or(HashSet::default(), |v| v.iter().cloned().collect()); + if new_a_nodes.len() != new_b_nodes.len() { + return false; + } + + if new_a_nodes.len() > 10 { + eprintln!("Too big instance, aborting"); + return true; //TODO: Very very very bad + } + + if build_and_check_containment_from_hashes( + a_bnodes_by_hash, + b_bnodes_by_hash, + a_to_b_mapping, + a, + b, + &new_a_nodes, + &mut new_b_nodes, + ) { + true + } else { + a_bnodes_by_hash.push((hash, new_a_nodes)); + false + } + } +} + +fn check_is_contained_focused<'a>( + a_to_b_mapping: &mut HashMap, + a_bnode_focus: EncodedTerm, + a: &'a HashSet, + b: &'a HashSet, +) -> bool { + let ts_a = a + .iter() + .filter(|t| t.subject == a_bnode_focus) + .chain(a.iter().filter(|t| t.object == a_bnode_focus)); + //TODO: these filters + for t_a in ts_a { + let subject = if t_a.subject.is_blank_node() { + if let Some(s_a) = a_to_b_mapping.get(&t_a.subject) { + *s_a + } else { + continue; // We skip for now + } + } else { + t_a.subject + }; + let object = if t_a.object.is_blank_node() { + if let Some(o_a) = a_to_b_mapping.get(&t_a.object) { + *o_a + } else { + continue; // We skip for now + } + } else { + t_a.object + }; + if !b.contains(&EncodedQuad::new( + subject, + t_a.predicate, + object, + t_a.graph_name, //TODO: support blank node graph names + )) { + //TODO + return false; + } + } + + true +} + +fn graph_blank_nodes(graph: &HashSet) -> Vec { + let mut blank_nodes: HashSet = HashSet::default(); + for t in graph { + if t.subject.is_blank_node() { + blank_nodes.insert(t.subject); + } + if t.object.is_blank_node() { + blank_nodes.insert(t.object); + } + } + blank_nodes.into_iter().collect() +} + +fn are_datasets_isomorphic(a: &MemoryStore, b: &MemoryStore) -> bool { + /* TODO if a.len() != b.len() { + return false; + }*/ + + // We check containment of everything buts triples with blank nodes + let mut a_bnodes_triples = HashSet::default(); + for t in a.encoded_quads() { + if t.subject.is_blank_node() || t.object.is_blank_node() { + a_bnodes_triples.insert(t); + } else if !b.contains_encoded(&t) { + return false; // Triple in a not in b without blank nodes + } + } + + let mut b_bnodes_triples = HashSet::default(); + for t in b.encoded_quads() { + if t.subject.is_blank_node() || t.object.is_blank_node() { + b_bnodes_triples.insert(t); + } else if !a.contains_encoded(&t) { + return false; // Triple in a not in b without blank nodes + } + } + + let mut a_bnodes_by_hash = HashMap::default(); + a_bnodes_by_hash.insert(0, graph_blank_nodes(&a_bnodes_triples)); + let mut b_bnodes_by_hash = HashMap::default(); + b_bnodes_by_hash.insert(0, graph_blank_nodes(&b_bnodes_triples)); + + for distance in 0..5 { + let max_size = a_bnodes_by_hash.values().map(Vec::len).max().unwrap_or(0); + if max_size < 2 { + break; // We only have small buckets + } + + a_bnodes_by_hash = split_hash_buckets(a_bnodes_by_hash, a, distance); + b_bnodes_by_hash = split_hash_buckets(b_bnodes_by_hash, b, distance); + + // Hashes should have the same size + if a_bnodes_by_hash.len() != b_bnodes_by_hash.len() { + return false; + } + } + + let mut sorted_a_bnodes_by_hash: Vec<_> = a_bnodes_by_hash.into_iter().collect(); + sorted_a_bnodes_by_hash.sort_by(|(_, l1), (_, l2)| l1.len().cmp(&l2.len())); + + build_and_check_containment_from_hashes( + &mut sorted_a_bnodes_by_hash, + &b_bnodes_by_hash, + &mut HashMap::default(), + &a_bnodes_triples, + &b_bnodes_triples, + &[], + &mut HashSet::default(), + ) +} diff --git a/lib/tests/service_test_cases.rs b/lib/tests/service_test_cases.rs index 057ac038..1fc78f84 100644 --- a/lib/tests/service_test_cases.rs +++ b/lib/tests/service_test_cases.rs @@ -192,7 +192,7 @@ fn literal(str: &str) -> Term { } fn make_store(reader: impl BufRead) -> Result { - let store = MemoryStore::default(); + let store = MemoryStore::new(); store .load_graph(reader, GraphSyntax::NTriples, None, None) .unwrap(); diff --git a/lib/tests/sparql_test_cases.rs b/lib/tests/sparql_test_cases.rs index a7335ca1..ba4b1bf3 100644 --- a/lib/tests/sparql_test_cases.rs +++ b/lib/tests/sparql_test_cases.rs @@ -152,7 +152,7 @@ fn sparql_w3c_query_evaluation_testsuite() -> Result<()> { if test_blacklist.contains(&test.id) { Ok(()) } else if test.kind == "QueryEvaluationTest" { - let store = MemoryStore::default(); + let store = MemoryStore::new(); if let Some(data) = &test.data { load_graph_to_store(&data, &store, None)?; } @@ -176,12 +176,12 @@ fn sparql_w3c_query_evaluation_testsuite() -> Result<()> { ))), Ok(result) => { let expected_graph = - load_sparql_query_result_graph(test.result.as_ref().unwrap()).map_err(|e| Error::msg(format!("Error constructing expected graph for {}: {}", test, e)))?; + load_sparql_query_result(test.result.as_ref().unwrap()).map_err(|e| Error::msg(format!("Error constructing expected graph for {}: {}", test, e)))?; let with_order = expected_graph - .triples_for_predicate(&rs::INDEX) + .quads_for_pattern(None, Some(&rs::INDEX), None, None) .next() .is_some(); - let actual_graph = to_graph(result, with_order).map_err(|e| Error::msg(format!("Error constructing result graph for {}: {}", test, e)))?; + let actual_graph = to_dataset(result, with_order).map_err(|e| Error::msg(format!("Error constructing result graph for {}: {}", test, e)))?; if actual_graph.is_isomorphic(&expected_graph) { Ok(()) } else { @@ -190,7 +190,7 @@ fn sparql_w3c_query_evaluation_testsuite() -> Result<()> { expected_graph, actual_graph, Query::parse(&read_file_to_string(&test.query)?, Some(&test.query)).unwrap(), - store_to_string(&store) + store ))) } } @@ -211,20 +211,10 @@ fn sparql_w3c_query_evaluation_testsuite() -> Result<()> { Ok(()) } -fn store_to_string(store: &MemoryStore) -> String { - store - .quads_for_pattern(None, None, None, None) - .map(|q| q.unwrap().to_string() + "\n") - .collect() -} - -fn load_graph(url: &str) -> Result { - let store = MemoryStore::default(); +fn load_graph(url: &str) -> Result { + let store = MemoryStore::new(); load_graph_to_store(url, &store, None)?; - Ok(store - .quads_for_pattern(None, None, None, Some(None)) - .map(|q| q.unwrap().into_triple()) - .collect()) + Ok(store) } fn load_graph_to_store( @@ -247,22 +237,15 @@ fn load_graph_to_store( store.load_graph(read_file(url)?, syntax, to_graph_name, Some(url)) } -fn load_sparql_query_result_graph(url: &str) -> Result { - let store = MemoryStore::default(); +fn load_sparql_query_result(url: &str) -> Result { if url.ends_with(".srx") { - for t in to_graph( + to_dataset( QueryResult::read(read_file(url)?, QueryResultSyntax::Xml)?, false, - )? { - store.insert(&t.in_graph(None))?; - } + ) } else { - load_graph_to_store(url, &store, None)?; + load_graph(url) } - Ok(store - .quads_for_pattern(None, None, None, Some(None)) - .map(|q| q.unwrap().into_triple()) - .collect()) } fn to_relative_path(url: &str) -> Result { @@ -306,151 +289,111 @@ fn read_file_to_string(url: &str) -> Result { Ok(string) } -mod rs { - use lazy_static::lazy_static; - use oxigraph::model::NamedNode; - - lazy_static! { - pub static ref RESULT_SET: NamedNode = - NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#ResultSet") - .unwrap(); - pub static ref RESULT_VARIABLE: NamedNode = NamedNode::parse( - "http://www.w3.org/2001/sw/DataAccess/tests/result-set#resultVariable" - ) - .unwrap(); - pub static ref SOLUTION: NamedNode = - NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#solution") - .unwrap(); - pub static ref BINDING: NamedNode = - NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#binding") - .unwrap(); - pub static ref VALUE: NamedNode = - NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#value") - .unwrap(); - pub static ref VARIABLE: NamedNode = - NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#variable") - .unwrap(); - pub static ref INDEX: NamedNode = - NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#index") - .unwrap(); - pub static ref BOOLEAN: NamedNode = - NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#boolean") - .unwrap(); - } -} - -fn to_graph(result: QueryResult<'_>, with_order: bool) -> Result { +fn to_dataset(result: QueryResult<'_>, with_order: bool) -> Result { match result { - QueryResult::Graph(graph) => graph.collect(), + QueryResult::Graph(graph) => graph.map(|t| t.map(|t| t.in_graph(None))).collect(), QueryResult::Boolean(value) => { - let mut graph = SimpleGraph::default(); + let store = MemoryStore::new(); let result_set = BlankNode::default(); - graph.insert(Triple::new( + store.insert(Quad::new( result_set, rdf::TYPE.clone(), rs::RESULT_SET.clone(), + None, )); - graph.insert(Triple::new( + store.insert(Quad::new( result_set, rs::BOOLEAN.clone(), Literal::from(value), + None, )); - Ok(graph) + Ok(store) } QueryResult::Bindings(solutions) => { - let mut graph = SimpleGraph::default(); + let store = MemoryStore::new(); let result_set = BlankNode::default(); - graph.insert(Triple::new( + store.insert(Quad::new( result_set, rdf::TYPE.clone(), rs::RESULT_SET.clone(), + None, )); for variable in solutions.variables() { - graph.insert(Triple::new( + store.insert(Quad::new( result_set, rs::RESULT_VARIABLE.clone(), Literal::new_simple_literal(variable.as_str()), + None, )); } for (i, solution) in solutions.enumerate() { let solution = solution?; let solution_id = BlankNode::default(); - graph.insert(Triple::new(result_set, rs::SOLUTION.clone(), solution_id)); + store.insert(Quad::new( + result_set, + rs::SOLUTION.clone(), + solution_id, + None, + )); for (variable, value) in solution.iter() { let binding = BlankNode::default(); - graph.insert(Triple::new(solution_id, rs::BINDING.clone(), binding)); - graph.insert(Triple::new(binding, rs::VALUE.clone(), value.clone())); - graph.insert(Triple::new( + store.insert(Quad::new(solution_id, rs::BINDING.clone(), binding, None)); + store.insert(Quad::new(binding, rs::VALUE.clone(), value.clone(), None)); + store.insert(Quad::new( binding, rs::VARIABLE.clone(), Literal::new_simple_literal(variable.as_str()), + None, )); } if with_order { - graph.insert(Triple::new( + store.insert(Quad::new( solution_id, rs::INDEX.clone(), Literal::from((i + 1) as i128), + None, )); } } - Ok(graph) + Ok(store) } } } -pub struct Test { - pub id: NamedNode, - pub kind: String, - pub name: Option, - pub comment: Option, - pub query: String, - pub data: Option, - pub graph_data: Vec, - pub service_data: Vec<(String, String)>, - pub result: Option, -} - -impl fmt::Display for Test { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.kind)?; - for name in &self.name { - write!(f, " named \"{}\"", name)?; - } - for comment in &self.comment { - write!(f, " with comment \"{}\"", comment)?; - } - write!(f, " on query {}", self.query)?; - for data in &self.data { - write!(f, " with data {}", data)?; - } - for data in &self.graph_data { - write!(f, " and graph data {}", data)?; - } - for result in &self.result { - write!(f, " and expected result {}", result)?; - } - Ok(()) - } -} - -pub struct TestManifest { - graph: SimpleGraph, - tests_to_do: Vec, - manifests_to_do: Vec, -} +mod rs { + use lazy_static::lazy_static; + use oxigraph::model::NamedNode; -impl TestManifest { - pub fn new(url: impl Into) -> TestManifest { - Self { - graph: SimpleGraph::default(), - tests_to_do: Vec::default(), - manifests_to_do: vec![url.into()], - } + lazy_static! { + pub static ref RESULT_SET: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#ResultSet") + .unwrap(); + pub static ref RESULT_VARIABLE: NamedNode = NamedNode::parse( + "http://www.w3.org/2001/sw/DataAccess/tests/result-set#resultVariable" + ) + .unwrap(); + pub static ref SOLUTION: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#solution") + .unwrap(); + pub static ref BINDING: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#binding") + .unwrap(); + pub static ref VALUE: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#value") + .unwrap(); + pub static ref VARIABLE: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#variable") + .unwrap(); + pub static ref INDEX: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#index") + .unwrap(); + pub static ref BOOLEAN: NamedNode = + NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/result-set#boolean") + .unwrap(); } } -pub mod mf { +mod mf { use lazy_static::lazy_static; use oxigraph::model::NamedNode; @@ -473,7 +416,7 @@ pub mod mf { } } -pub mod qt { +mod qt { use lazy_static::lazy_static; use oxigraph::model::NamedNode; @@ -495,6 +438,57 @@ pub mod qt { } } +struct Test { + id: NamedNode, + kind: String, + name: Option, + comment: Option, + query: String, + data: Option, + graph_data: Vec, + service_data: Vec<(String, String)>, + result: Option, +} + +impl fmt::Display for Test { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.kind)?; + for name in &self.name { + write!(f, " named \"{}\"", name)?; + } + for comment in &self.comment { + write!(f, " with comment \"{}\"", comment)?; + } + write!(f, " on query {}", self.query)?; + for data in &self.data { + write!(f, " with data {}", data)?; + } + for data in &self.graph_data { + write!(f, " and graph data {}", data)?; + } + for result in &self.result { + write!(f, " and expected result {}", result)?; + } + Ok(()) + } +} + +struct TestManifest { + graph: MemoryStore, + tests_to_do: Vec, + manifests_to_do: Vec, +} + +impl TestManifest { + fn new(url: impl Into) -> TestManifest { + Self { + graph: MemoryStore::new(), + tests_to_do: Vec::default(), + manifests_to_do: vec![url.into()], + } + } +} + impl Iterator for TestManifest { type Item = Result; @@ -502,94 +496,98 @@ impl Iterator for TestManifest { match self.tests_to_do.pop() { Some(Term::NamedNode(test_node)) => { let test_subject = NamedOrBlankNode::from(test_node.clone()); - let kind = match self - .graph - .object_for_subject_predicate(&test_subject, &rdf::TYPE) - { - Some(Term::NamedNode(c)) => match c.as_str().split('#').last() { - Some(k) => k.to_string(), - None => return self.next(), //We ignore the test - }, - _ => return self.next(), //We ignore the test - }; - let name = match self - .graph - .object_for_subject_predicate(&test_subject, &mf::NAME) + let kind = + match object_for_subject_predicate(&self.graph, &test_subject, &rdf::TYPE) { + Some(Term::NamedNode(c)) => match c.as_str().split('#').last() { + Some(k) => k.to_string(), + None => return self.next(), //We ignore the test + }, + _ => return self.next(), //We ignore the test + }; + let name = match object_for_subject_predicate(&self.graph, &test_subject, &mf::NAME) { Some(Term::Literal(c)) => Some(c.value().to_string()), _ => None, }; - let comment = match self - .graph - .object_for_subject_predicate(&test_subject, &rdfs::COMMENT) - { + let comment = match object_for_subject_predicate( + &self.graph, + &test_subject, + &rdfs::COMMENT, + ) { Some(Term::Literal(c)) => Some(c.value().to_string()), _ => None, }; - let (query, data, graph_data, service_data) = match self - .graph - .object_for_subject_predicate(&test_subject, &*mf::ACTION) - { - Some(Term::NamedNode(n)) => (n.as_str().to_owned(), None, vec![], vec![]), - Some(Term::BlankNode(n)) => { - let n = n.clone().into(); - let query = match self.graph.object_for_subject_predicate(&n, &qt::QUERY) { - Some(Term::NamedNode(q)) => q.as_str().to_owned(), - Some(_) => return Some(Err(Error::msg("invalid query"))), - None => return Some(Err(Error::msg("query not found"))), - }; - let data = match self.graph.object_for_subject_predicate(&n, &qt::DATA) { - Some(Term::NamedNode(q)) => Some(q.as_str().to_owned()), - _ => None, - }; - let graph_data = self - .graph - .objects_for_subject_predicate(&n, &qt::GRAPH_DATA) - .filter_map(|g| match g { - Term::NamedNode(q) => Some(q.as_str().to_owned()), - _ => None, - }) - .collect(); - let service_data = self - .graph - .objects_for_subject_predicate(&n, &qt::SERVICE_DATA) - .filter_map(|g| match g { - Term::NamedNode(g) => Some(g.clone().into()), - Term::BlankNode(g) => Some(g.clone().into()), - _ => None, - }) - .filter_map(|g| { - if let ( - Some(Term::NamedNode(endpoint)), - Some(Term::NamedNode(data)), - ) = ( - self.graph.object_for_subject_predicate(&g, &qt::ENDPOINT), - self.graph.object_for_subject_predicate(&g, &qt::DATA), - ) { - Some((endpoint.as_str().to_owned(), data.as_str().to_owned())) - } else { - None - } - }) - .collect(); - (query, data, graph_data, service_data) - } - Some(_) => return Some(Err(Error::msg("invalid action"))), - None => { - return Some(Err(Error::msg(format!( - "action not found for test {}", - test_subject - )))); - } - }; - let result = match self - .graph - .object_for_subject_predicate(&test_subject, &*mf::RESULT) - { - Some(Term::NamedNode(n)) => Some(n.as_str().to_owned()), - Some(_) => return Some(Err(Error::msg("invalid result"))), - None => None, - }; + let (query, data, graph_data, service_data) = + match object_for_subject_predicate(&self.graph, &test_subject, &*mf::ACTION) { + Some(Term::NamedNode(n)) => (n.as_str().to_owned(), None, vec![], vec![]), + Some(Term::BlankNode(n)) => { + let n = n.clone().into(); + let query = + match object_for_subject_predicate(&self.graph, &n, &qt::QUERY) { + Some(Term::NamedNode(q)) => q.as_str().to_owned(), + Some(_) => return Some(Err(Error::msg("invalid query"))), + None => return Some(Err(Error::msg("query not found"))), + }; + let data = + match object_for_subject_predicate(&self.graph, &n, &qt::DATA) { + Some(Term::NamedNode(q)) => Some(q.as_str().to_owned()), + _ => None, + }; + let graph_data = + objects_for_subject_predicate(&self.graph, &n, &qt::GRAPH_DATA) + .filter_map(|g| match g { + Term::NamedNode(q) => Some(q.as_str().to_owned()), + _ => None, + }) + .collect(); + let service_data = + objects_for_subject_predicate(&self.graph, &n, &qt::SERVICE_DATA) + .filter_map(|g| match g { + Term::NamedNode(g) => Some(g.into()), + Term::BlankNode(g) => Some(g.into()), + _ => None, + }) + .filter_map(|g| { + if let ( + Some(Term::NamedNode(endpoint)), + Some(Term::NamedNode(data)), + ) = ( + object_for_subject_predicate( + &self.graph, + &g, + &qt::ENDPOINT, + ), + object_for_subject_predicate( + &self.graph, + &g, + &qt::DATA, + ), + ) { + Some(( + endpoint.as_str().to_owned(), + data.as_str().to_owned(), + )) + } else { + None + } + }) + .collect(); + (query, data, graph_data, service_data) + } + Some(_) => return Some(Err(Error::msg("invalid action"))), + None => { + return Some(Err(Error::msg(format!( + "action not found for test {}", + test_subject + )))); + } + }; + let result = + match object_for_subject_predicate(&self.graph, &test_subject, &*mf::RESULT) { + Some(Term::NamedNode(n)) => Some(n.as_str().to_owned()), + Some(_) => return Some(Err(Error::msg("invalid result"))), + None => None, + }; Some(Ok(Test { id: test_node, kind, @@ -608,16 +606,11 @@ impl Iterator for TestManifest { Some(url) => { let manifest = NamedOrBlankNode::from(NamedNode::parse(url.clone()).unwrap()); - match load_graph(&url) { - Ok(g) => self.graph.extend(g.into_iter()), - Err(e) => return Some(Err(e)), + if let Err(e) = load_graph_to_store(&url, &self.graph, None) { + return Some(Err(e)); } - // New manifests - match self - .graph - .object_for_subject_predicate(&manifest, &*mf::INCLUDE) - { + match object_for_subject_predicate(&self.graph, &manifest, &*mf::INCLUDE) { Some(Term::BlankNode(list)) => { self.manifests_to_do.extend( RdfListIterator::iter(&self.graph, list.clone().into()) @@ -632,10 +625,7 @@ impl Iterator for TestManifest { } // New tests - match self - .graph - .object_for_subject_predicate(&manifest, &*mf::ENTRIES) - { + match object_for_subject_predicate(&self.graph, &manifest, &*mf::ENTRIES) { Some(Term::BlankNode(list)) => { self.tests_to_do.extend(RdfListIterator::iter( &self.graph, @@ -659,13 +649,13 @@ impl Iterator for TestManifest { } } -pub struct RdfListIterator<'a> { - graph: &'a SimpleGraph, +struct RdfListIterator<'a> { + graph: &'a MemoryStore, current_node: Option, } impl<'a> RdfListIterator<'a> { - fn iter(graph: &'a SimpleGraph, root: NamedOrBlankNode) -> RdfListIterator<'a> { + fn iter(graph: &'a MemoryStore, root: NamedOrBlankNode) -> RdfListIterator<'a> { RdfListIterator { graph, current_node: Some(root), @@ -679,19 +669,15 @@ impl<'a> Iterator for RdfListIterator<'a> { fn next(&mut self) -> Option { match self.current_node.clone() { Some(current) => { - let result = self - .graph - .object_for_subject_predicate(¤t, &rdf::FIRST); - self.current_node = match self - .graph - .object_for_subject_predicate(¤t, &rdf::REST) - { - Some(Term::NamedNode(ref n)) if *n == *rdf::NIL => None, - Some(Term::NamedNode(n)) => Some(n.clone().into()), - Some(Term::BlankNode(n)) => Some(n.clone().into()), - _ => None, - }; - result.cloned() + let result = object_for_subject_predicate(&self.graph, ¤t, &rdf::FIRST); + self.current_node = + match object_for_subject_predicate(&self.graph, ¤t, &rdf::REST) { + Some(Term::NamedNode(ref n)) if *n == *rdf::NIL => None, + Some(Term::NamedNode(n)) => Some(n.into()), + Some(Term::BlankNode(n)) => Some(n.into()), + _ => None, + }; + result } None => None, } @@ -711,7 +697,7 @@ impl StaticServiceHandler { .iter() .map(|(name, data)| { let name = NamedNode::parse(name)?; - let store = MemoryStore::default(); + let store = MemoryStore::new(); load_graph_to_store(&data, &store, None)?; Ok((name, store)) }) @@ -749,3 +735,21 @@ impl ServiceHandler for StaticServiceHandler { } } } + +fn object_for_subject_predicate( + store: &MemoryStore, + subject: &NamedOrBlankNode, + predicate: &NamedNode, +) -> Option { + objects_for_subject_predicate(store, subject, predicate).next() +} + +fn objects_for_subject_predicate( + store: &MemoryStore, + subject: &NamedOrBlankNode, + predicate: &NamedNode, +) -> impl Iterator { + store + .quads_for_pattern(Some(subject), Some(predicate), None, None) + .map(|t| t.object_owned()) +} diff --git a/lib/tests/wasm.rs b/lib/tests/wasm.rs index 8431b5fe..0157702e 100644 --- a/lib/tests/wasm.rs +++ b/lib/tests/wasm.rs @@ -8,7 +8,7 @@ mod test { #[wasm_bindgen_test] fn simple() { - let store = MemoryStore::default(); + let store = MemoryStore::new(); // insertion let ex = NamedNode::parse("http://example.com").unwrap(); @@ -33,7 +33,7 @@ mod test { #[wasm_bindgen_test] fn now() { - let store = MemoryStore::default(); + let store = MemoryStore::new(); let prepared_query = store .prepare_query( "SELECT (YEAR(NOW()) AS ?y) WHERE {}",