Adds isomorphism computation to MemoryDataset and drops SimpleGraph

pull/41/head
Tpt 5 years ago
parent 5039da163b
commit 69f94777b6
  1. 14
      js/src/store.rs
  2. 239
      lib/benches/sparql_query.rs
  3. 2
      lib/benches/store.rs
  4. 8
      lib/src/lib.rs
  5. 178
      lib/src/model/graph.rs
  6. 289
      lib/src/model/isomorphism.rs
  7. 3
      lib/src/model/mod.rs
  8. 370
      lib/src/store/memory.rs
  9. 2
      lib/tests/service_test_cases.rs
  10. 476
      lib/tests/sparql_test_cases.rs
  11. 4
      lib/tests/wasm.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<bool, JsValue> {
@ -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::<Result<Vec<_>,Error>>().map_err(to_err)?.into_boxed_slice())
).map(|v| JsQuad::from(v).into()).collect::<Vec<_>>().into_boxed_slice())
}
pub fn query(&self, query: &str) -> Result<JsValue, JsValue> {

@ -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<String> {
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<String> {
Ok(string)
}
fn load_graph(url: &str) -> Result<SimpleGraph> {
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<Term>,
manifests_to_do: Vec<String>,
}
@ -185,7 +190,7 @@ struct TestManifest {
impl TestManifest {
fn new(url: impl Into<String>) -> 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<Result<Test>> {
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<NamedOrBlankNode>,
}
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<Term> {
match self.current_node.clone() {
Some(current) => {
let result = self
.graph
.object_for_subject_predicate(&current, &rdf::FIRST);
self.current_node = match self
.graph
.object_for_subject_predicate(&current, &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, &current, &rdf::FIRST);
self.current_node =
match object_for_subject_predicate(&self.graph, &current, &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<Term> {
objects_for_subject_predicate(store, subject, predicate).next()
}
fn objects_for_subject_predicate(
store: &MemoryStore,
subject: &NamedOrBlankNode,
predicate: &NamedNode,
) -> impl Iterator<Item = Term> {
store
.quads_for_pattern(Some(subject), Some(predicate), None, None)
.map(|t| t.object_owned())
}

@ -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());
}
});
});

@ -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<Vec<Quad>> = store.quads_for_pattern(None, None, None, None).collect();
//! assert_eq!(vec![quad], results?);
//! let results: Vec<Quad> = 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())?;

@ -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<Triple> = graph.triples_for_subject(&ex.into()).cloned().collect();
/// assert_eq!(vec![triple], results);
/// ```
#[derive(Eq, PartialEq, Debug, Clone, Default)]
pub struct SimpleGraph {
triples: HashSet<Triple>,
}
impl SimpleGraph {
/// Returns all triples contained by the graph
pub fn iter(&self) -> impl Iterator<Item = &Triple> {
self.triples.iter()
}
pub fn triples_for_subject<'a>(
&'a self,
subject: &'a NamedOrBlankNode,
) -> impl Iterator<Item = &Triple> + '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<Item = &Term> + '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<Item = &NamedNode> + '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<Item = &Triple> + '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<Item = &NamedOrBlankNode> + '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<Item = &Triple> + '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 = <HashSet<Triple> 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<Triple> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.triples.iter()
}
}
impl FromIterator<Triple> for SimpleGraph {
fn from_iter<I: IntoIterator<Item = Triple>>(iter: I) -> Self {
Self {
triples: HashSet::from_iter(iter),
}
}
}
impl Extend<Triple> for SimpleGraph {
fn extend<I: IntoIterator<Item = Triple>>(&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(())
}
}

@ -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<Item = SubjectPredicate<'a>> + '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<Item = PredicateObject<'a>> + 'a {
graph.triples_for_subject(subject).map(|t| PredicateObject {
predicate: t.predicate(),
object: t.object(),
})
}
fn split_hash_buckets<'a>(
bnodes_by_hash: HashMap<u64, Vec<&'a BlankNode>>,
graph: &'a SimpleGraph,
distance: usize,
) -> HashMap<u64, Vec<&'a BlankNode>> {
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<PredicateObject<'_>> = 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<SubjectPredicate<'_>> = 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<u64, Vec<&'a BlankNode>>,
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::<Vec<_>>();
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(),
)
}

@ -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;

@ -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<Vec<Quad>> = store.quads_for_pattern(None, None, None, None).collect();
/// assert_eq!(vec![quad], results?);
/// let results: Vec<Quad> = 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<Vec<Quad>> = store.quads_for_pattern(None, None, None, None).collect();
/// assert_eq!(vec![quad], results?);
/// let results: Vec<Quad> = 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<Option<&NamedOrBlankNode>>,
) -> impl Iterator<Item = Result<Quad>> {
) -> impl Iterator<Item = Quad> {
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"<http://example.com> <http://example.com> <http://example.com> .";
/// store.load_graph(file.as_ref(), GraphSyntax::NTriples, None, None);
///
/// // quad filter
/// let results: Result<Vec<Quad>> = store.quads_for_pattern(None, None, None, None).collect();
/// let results: Vec<Quad> = 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"<http://example.com> <http://example.com> <http://example.com> <http://example.com> .";
/// store.load_dataset(file.as_ref(), DatasetSyntax::NQuads, None);
///
/// // quad filter
/// let results: Result<Vec<Quad>> = store.quads_for_pattern(None, None, None, None).collect();
/// let results: Vec<Quad> = 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"<http://example.com> <http://example.com> <http://example.com> .";
@ -834,9 +849,9 @@ impl<'a> MemoryTransaction<'a> {
/// })?;
///
/// // quad filter
/// let results: Result<Vec<Quad>> = store.quads_for_pattern(None, None, None, None).collect();
/// let results: Vec<Quad> = 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"<http://example.com> <http://example.com> <http://example.com> <http://example.com> .";
/// store.load_dataset(file.as_ref(), DatasetSyntax::NQuads, None);
///
/// // quad filter
/// let results: Result<Vec<Quad>> = store.quads_for_pattern(None, None, None, None).collect();
/// let results: Vec<Quad> = 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<Quad> for MemoryStore {
fn from_iter<I: IntoIterator<Item = Quad>>(iter: I) -> Self {
let mut store = MemoryStore::new();
store.extend(iter);
store
}
}
impl Extend<Quad> for MemoryStore {
fn extend<T: IntoIterator<Item = Quad>>(&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<u64, Vec<EncodedTerm>>,
graph: &MemoryStore,
distance: usize,
) -> HashMap<u64, Vec<EncodedTerm>> {
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<u8> {
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<EncodedTerm>)>,
b_bnodes_by_hash: &'a HashMap<u64, Vec<EncodedTerm>>,
a_to_b_mapping: &mut HashMap<EncodedTerm, EncodedTerm>,
a: &'a HashSet<EncodedQuad>,
b: &'a HashSet<EncodedQuad>,
current_a_nodes: &[EncodedTerm],
current_b_nodes: &mut HashSet<EncodedTerm>,
) -> bool {
if let Some((a_node, remaining_a_node)) = current_a_nodes.split_last() {
let b_nodes = current_b_nodes.iter().cloned().collect::<Vec<_>>();
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<EncodedTerm, EncodedTerm>,
a_bnode_focus: EncodedTerm,
a: &'a HashSet<EncodedQuad>,
b: &'a HashSet<EncodedQuad>,
) -> 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<EncodedQuad>) -> Vec<EncodedTerm> {
let mut blank_nodes: HashSet<EncodedTerm> = 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(),
)
}

@ -192,7 +192,7 @@ fn literal(str: &str) -> Term {
}
fn make_store(reader: impl BufRead) -> Result<MemoryStore> {
let store = MemoryStore::default();
let store = MemoryStore::new();
store
.load_graph(reader, GraphSyntax::NTriples, None, None)
.unwrap();

@ -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<SimpleGraph> {
let store = MemoryStore::default();
fn load_graph(url: &str) -> Result<MemoryStore> {
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<SimpleGraph> {
let store = MemoryStore::default();
fn load_sparql_query_result(url: &str) -> Result<MemoryStore> {
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<String> {
@ -306,151 +289,111 @@ fn read_file_to_string(url: &str) -> Result<String> {
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<SimpleGraph> {
fn to_dataset(result: QueryResult<'_>, with_order: bool) -> Result<MemoryStore> {
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<String>,
pub comment: Option<String>,
pub query: String,
pub data: Option<String>,
pub graph_data: Vec<String>,
pub service_data: Vec<(String, String)>,
pub result: Option<String>,
}
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<Term>,
manifests_to_do: Vec<String>,
}
mod rs {
use lazy_static::lazy_static;
use oxigraph::model::NamedNode;
impl TestManifest {
pub fn new(url: impl Into<String>) -> 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<String>,
comment: Option<String>,
query: String,
data: Option<String>,
graph_data: Vec<String>,
service_data: Vec<(String, String)>,
result: Option<String>,
}
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<Term>,
manifests_to_do: Vec<String>,
}
impl TestManifest {
fn new(url: impl Into<String>) -> TestManifest {
Self {
graph: MemoryStore::new(),
tests_to_do: Vec::default(),
manifests_to_do: vec![url.into()],
}
}
}
impl Iterator for TestManifest {
type Item = Result<Test>;
@ -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<NamedOrBlankNode>,
}
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<Term> {
match self.current_node.clone() {
Some(current) => {
let result = self
.graph
.object_for_subject_predicate(&current, &rdf::FIRST);
self.current_node = match self
.graph
.object_for_subject_predicate(&current, &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, &current, &rdf::FIRST);
self.current_node =
match object_for_subject_predicate(&self.graph, &current, &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<Term> {
objects_for_subject_predicate(store, subject, predicate).next()
}
fn objects_for_subject_predicate(
store: &MemoryStore,
subject: &NamedOrBlankNode,
predicate: &NamedNode,
) -> impl Iterator<Item = Term> {
store
.quads_for_pattern(Some(subject), Some(predicate), None, None)
.map(|t| t.object_owned())
}

@ -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 {}",

Loading…
Cancel
Save