From cc7c92092f33291e60220a6e69a6d99720c2c9ac Mon Sep 17 00:00:00 2001 From: Tpt Date: Fri, 29 May 2020 14:37:22 +0200 Subject: [PATCH] Provides a nice API for SPARQL SELECT solutions handling --- js/src/store.rs | 18 ++-- lib/src/lib.rs | 5 +- lib/src/model/named_node.rs | 1 - lib/src/sparql/eval.rs | 41 +++------ lib/src/sparql/json_results.rs | 68 +++++++-------- lib/src/sparql/mod.rs | 19 +++-- lib/src/sparql/model.rs | 145 ++++++++++++++++++++++++++++++-- lib/src/sparql/xml_results.rs | 83 +++++++++--------- lib/src/store/memory.rs | 10 +-- lib/src/store/rocksdb.rs | 4 +- lib/src/store/sled.rs | 5 +- lib/tests/service_test_cases.rs | 93 +++++++++----------- lib/tests/sparql_test_cases.rs | 41 +++++---- lib/tests/wasm.rs | 12 ++- 14 files changed, 316 insertions(+), 229 deletions(-) diff --git a/js/src/store.rs b/js/src/store.rs index 293ecfa0..c12ca403 100644 --- a/js/src/store.rs +++ b/js/src/store.rs @@ -116,18 +116,16 @@ impl JsMemoryStore { .map_err(to_err)?; let results = query.exec().map_err(to_err)?; let output = match results { - QueryResult::Bindings(bindings) => { - let (variables, iter) = bindings.destruct(); - let variables: Vec = - variables.into_iter().map(|v| v.as_str().into()).collect(); + QueryResult::Bindings(solutions) => { let results = Array::new(); - for values in iter { - let values = values.map_err(to_err)?; + for solution in solutions { + let solution = solution.map_err(to_err)?; let result = Map::new(); - for (variable, value) in variables.iter().zip(values) { - if let Some(value) = value { - result.set(variable, &JsTerm::from(value).into()); - } + for (variable, value) in solution.iter() { + result.set( + &variable.as_str().into(), + &JsTerm::from(value.clone()).into(), + ); } results.push(&result.into()); } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 9a1395d3..87a589d1 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -33,9 +33,8 @@ //! //! // SPARQL query //! let prepared_query = store.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default())?; -//! let results = prepared_query.exec()?; -//! if let QueryResult::Bindings(results) = results { -//! assert_eq!(results.into_values_iter().next().unwrap()?[0], Some(ex.into())); +//! if let QueryResult::Bindings(mut solutions) = prepared_query.exec()? { +//! assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into())); //! } //! # Result::Ok(()) //! ``` diff --git a/lib/src/model/named_node.rs b/lib/src/model/named_node.rs index 0bd5da82..749c199d 100644 --- a/lib/src/model/named_node.rs +++ b/lib/src/model/named_node.rs @@ -13,7 +13,6 @@ use std::fmt; /// NamedNode::parse("http://example.com/foo").unwrap().to_string() /// ) /// ``` -/// #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] pub struct NamedNode { iri: String, diff --git a/lib/src/sparql/eval.rs b/lib/src/sparql/eval.rs index 47f3b3fb..fa48ab8a 100644 --- a/lib/src/sparql/eval.rs +++ b/lib/src/sparql/eval.rs @@ -1564,13 +1564,13 @@ impl<'a, S: ReadableEncodedStore + 'a> SimpleEvaluator { &'b self, iter: EncodedTuplesIterator<'b>, variables: Vec, - ) -> BindingsIterator<'b> + ) -> QuerySolutionsIterator<'b> where 'a: 'b, { let eval = self; let tuple_size = variables.len(); - BindingsIterator::new( + QuerySolutionsIterator::new( variables, Box::new(iter.map(move |values| { let mut result = vec![None; tuple_size]; @@ -1584,40 +1584,25 @@ impl<'a, S: ReadableEncodedStore + 'a> SimpleEvaluator { ) } - // this is used to encode results froma BindingIterator into an EncodedTuplesIterator. This happens when SERVICE clauses are evaluated + // this is used to encode results from a BindingIterator into an EncodedTuplesIterator. This happens when SERVICE clauses are evaluated fn encode_bindings<'b>( &'b self, variables: &'b [Variable], - iter: BindingsIterator<'b>, + iter: QuerySolutionsIterator<'b>, ) -> EncodedTuplesIterator<'b> where 'a: 'b, { - let (binding_variables, iter) = BindingsIterator::destruct(iter); - let mut combined_variables = variables.to_vec(); - for v in binding_variables.clone() { - if !combined_variables.contains(&v) { - combined_variables.resize(combined_variables.len() + 1, v); - } - } - Box::new(iter.map(move |terms| { + Box::new(iter.map(move |solution| { let mut encoder = self.dataset.encoder(); - let mut encoded_terms = EncodedTuple::with_capacity(combined_variables.len()); - for (i, term_option) in terms?.into_iter().enumerate() { - match term_option { - None => (), - Some(term) => { - if let Ok(encoded) = encoder.encode_term(&term) { - let variable = binding_variables[i].clone(); - put_variable_value( - &variable, - &combined_variables, - encoded, - &mut encoded_terms, - ) - } - } - } + let mut encoded_terms = EncodedTuple::with_capacity(variables.len()); + for (variable, term) in solution?.iter() { + put_variable_value( + variable, + variables, + encoder.encode_term(term)?, + &mut encoded_terms, + ) } Ok(encoded_terms) })) diff --git a/lib/src/sparql/json_results.rs b/lib/src/sparql/json_results.rs index 8eb1996c..6c5f2af5 100644 --- a/lib/src/sparql/json_results.rs +++ b/lib/src/sparql/json_results.rs @@ -13,11 +13,10 @@ pub fn write_json_results(results: QueryResult<'_>, mut sink: W) -> Re sink.write_all(if value { b"true" } else { b"false" })?; sink.write_all(b"}")?; } - QueryResult::Bindings(bindings) => { - let (variables, results) = bindings.destruct(); + QueryResult::Bindings(solutions) => { sink.write_all(b"{\"head\":{\"vars\":[")?; let mut start_vars = true; - for variable in &variables { + for variable in solutions.variables() { if start_vars { start_vars = false; } else { @@ -27,7 +26,7 @@ pub fn write_json_results(results: QueryResult<'_>, mut sink: W) -> Re } sink.write_all(b"]},\"results\":{\"bindings\":[")?; let mut start_bindings = true; - for result in results { + for solution in solutions { if start_bindings { start_bindings = false; } else { @@ -35,42 +34,37 @@ pub fn write_json_results(results: QueryResult<'_>, mut sink: W) -> Re } sink.write_all(b"{")?; - let result = result?; + let solution = solution?; let mut start_binding = true; - for (i, value) in result.into_iter().enumerate() { - if let Some(term) = value { - if start_binding { - start_binding = false; - } else { - sink.write_all(b",")?; + for (variable, value) in solution.iter() { + if start_binding { + start_binding = false; + } else { + sink.write_all(b",")?; + } + write_escaped_json_string(variable.as_str(), &mut sink)?; + match value { + Term::NamedNode(uri) => { + sink.write_all(b":{\"type\":\"uri\",\"value\":")?; + write_escaped_json_string(uri.as_str(), &mut sink)?; + sink.write_all(b"}")?; } - write_escaped_json_string(variables[i].as_str(), &mut sink)?; - match term { - Term::NamedNode(uri) => { - sink.write_all(b":{\"type\":\"uri\",\"value\":")?; - write_escaped_json_string(uri.as_str(), &mut sink)?; - sink.write_all(b"}")?; - } - Term::BlankNode(bnode) => { - sink.write_all(b":{\"type\":\"bnode\",\"value\":")?; - write!(sink, "{}", bnode.as_str())?; - sink.write_all(b"}")?; - } - Term::Literal(literal) => { - sink.write_all(b":{\"type\":\"literal\",\"value\":")?; - write_escaped_json_string(literal.value(), &mut sink)?; - if let Some(language) = literal.language() { - sink.write_all(b",\"xml:lang\":")?; - write_escaped_json_string(language, &mut sink)?; - } else if !literal.is_plain() { - sink.write_all(b",\"datatype\":")?; - write_escaped_json_string( - literal.datatype().as_str(), - &mut sink, - )?; - } - sink.write_all(b"}")?; + Term::BlankNode(bnode) => { + sink.write_all(b":{\"type\":\"bnode\",\"value\":")?; + write!(sink, "{}", bnode.as_str())?; + sink.write_all(b"}")?; + } + Term::Literal(literal) => { + sink.write_all(b":{\"type\":\"literal\",\"value\":")?; + write_escaped_json_string(literal.value(), &mut sink)?; + if let Some(language) = literal.language() { + sink.write_all(b",\"xml:lang\":")?; + write_escaped_json_string(language, &mut sink)?; + } else if !literal.is_plain() { + sink.write_all(b",\"datatype\":")?; + write_escaped_json_string(literal.datatype().as_str(), &mut sink)?; } + sink.write_all(b"}")?; } } } diff --git a/lib/src/sparql/mod.rs b/lib/src/sparql/mod.rs index b9f4c722..d276c087 100644 --- a/lib/src/sparql/mod.rs +++ b/lib/src/sparql/mod.rs @@ -21,7 +21,10 @@ use crate::Result; use oxiri::Iri; pub use crate::sparql::algebra::GraphPattern; -pub use crate::sparql::model::BindingsIterator; +pub use crate::sparql::model::QuerySolution; +pub use crate::sparql::model::QuerySolutionsIterator; +#[deprecated(note = "Please directly use QuerySolutionsIterator type instead")] +pub type BindingsIterator<'a> = QuerySolutionsIterator<'a>; pub use crate::sparql::model::QueryResult; pub use crate::sparql::model::QueryResultSyntax; pub use crate::sparql::model::Variable; @@ -161,17 +164,17 @@ pub trait ServiceHandler { &'a self, service_name: &NamedNode, graph_pattern: &'a GraphPattern, - ) -> Result>; + ) -> Result>; } -impl Fn(&NamedNode, &'a GraphPattern) -> Result>> ServiceHandler - for F +impl Fn(&NamedNode, &'a GraphPattern) -> Result>> + ServiceHandler for F { fn handle<'a>( &'a self, service_name: &NamedNode, graph_pattern: &'a GraphPattern, - ) -> Result> { + ) -> Result> { self(service_name, graph_pattern) } } @@ -179,7 +182,11 @@ impl Fn(&NamedNode, &'a GraphPattern) -> Result> struct EmptyServiceHandler; impl ServiceHandler for EmptyServiceHandler { - fn handle<'a>(&'a self, _: &NamedNode, _: &'a GraphPattern) -> Result> { + fn handle<'a>( + &'a self, + _: &NamedNode, + _: &'a GraphPattern, + ) -> Result> { Err(Error::msg("The SERVICE feature is not implemented")) } } diff --git a/lib/src/sparql/model.rs b/lib/src/sparql/model.rs index 1b5c0c00..575fcc78 100644 --- a/lib/src/sparql/model.rs +++ b/lib/src/sparql/model.rs @@ -9,11 +9,12 @@ use rio_turtle::{NTriplesFormatter, TurtleFormatter}; use rio_xml::RdfXmlFormatter; use std::fmt; use std::io::{BufRead, Write}; +use std::rc::Rc; /// Results of a [SPARQL query](https://www.w3.org/TR/sparql11-query/) pub enum QueryResult<'a> { /// Results of a [SELECT](https://www.w3.org/TR/sparql11-query/#select) query - Bindings(BindingsIterator<'a>), + Bindings(QuerySolutionsIterator<'a>), /// Result of a [ASK](https://www.w3.org/TR/sparql11-query/#ask) query Boolean(bool), /// Results of a [CONSTRUCT](https://www.w3.org/TR/sparql11-query/#construct) or [DESCRIBE](https://www.w3.org/TR/sparql11-query/#describe) query @@ -119,24 +120,55 @@ impl FileSyntax for QueryResultSyntax { } } -/// An iterator over results bindings -pub struct BindingsIterator<'a> { - variables: Vec, +/// An iterator over query result solutions +/// +/// ``` +/// use oxigraph::{MemoryStore, Result}; +/// use oxigraph::sparql::{PreparedQuery, QueryResult, QueryOptions, Variable}; +/// +/// let store = MemoryStore::new(); +/// let prepared_query = store.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default())?; +/// if let QueryResult::Bindings(solutions) = prepared_query.exec()? { +/// for solution in solutions { +/// println!("{:?}", solution?.get("s")); +/// } +/// } +/// # Result::Ok(()) +/// ``` +pub struct QuerySolutionsIterator<'a> { + variables: Rc>, iter: Box>>> + 'a>, } -impl<'a> BindingsIterator<'a> { +impl<'a> QuerySolutionsIterator<'a> { pub fn new( variables: Vec, iter: Box>>> + 'a>, ) -> Self { - Self { variables, iter } + Self { + variables: Rc::new(variables), + iter, + } } + /// The variables used in the solutions + /// + /// ``` + /// use oxigraph::{MemoryStore, Result}; + /// use oxigraph::sparql::{PreparedQuery, QueryResult, QueryOptions, Variable}; + /// + /// let store = MemoryStore::new(); + /// let prepared_query = store.prepare_query("SELECT ?s ?o WHERE { ?s ?p ?o }", QueryOptions::default())?; + /// if let QueryResult::Bindings(solutions) = prepared_query.exec()? { + /// assert_eq!(solutions.variables(), &[Variable::new("s"), Variable::new("o")]); + /// } + /// # Result::Ok(()) + /// ``` pub fn variables(&self) -> &[Variable] { &*self.variables } + #[deprecated(note = "Please directly use QuerySolutionsIterator as an iterator instead")] pub fn into_values_iter(self) -> Box>>> + 'a> { self.iter } @@ -147,11 +179,108 @@ impl<'a> BindingsIterator<'a> { Vec, Box>>> + 'a>, ) { - (self.variables, self.iter) + ((*self.variables).clone(), self.iter) + } +} + +impl<'a> Iterator for QuerySolutionsIterator<'a> { + type Item = Result; + + fn next(&mut self) -> Option> { + Some(self.iter.next()?.map(|values| QuerySolution { + values, + variables: self.variables.clone(), + })) + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +/// Tuple associating variables and terms that are the result of a SPARQL query. +/// +/// It is the equivalent of a row in SQL. +pub struct QuerySolution { + values: Vec>, + variables: Rc>, +} + +impl QuerySolution { + /// Returns a value for a given position in the tuple (`usize`) or a given variable name (`&str` or `Variable`) + /// + /// ```ignore + /// let foo = solution.get("foo"); // Get the value of the variable ?foo if it exists + /// let first = solution.get(1); // Get the value of the second column if it exists + /// ``` + pub fn get(&self, index: impl VariableSolutionIndex) -> Option<&Term> { + self.values.get(index.index(self)?).and_then(|e| e.as_ref()) + } + + /// The number of variables which are bind + pub fn len(&self) -> usize { + self.values.len() + } + + /// Is this binding empty? + pub fn is_empty(&self) -> bool { + self.values.is_empty() + } + + /// Returns an iterator over bound variables + pub fn iter(&self) -> impl Iterator { + self.values + .iter() + .enumerate() + .filter_map(move |(i, value)| { + if let Some(value) = value { + Some((&self.variables[i], value)) + } else { + None + } + }) + } +} + +/// A utility trait to get values for a given variable or tuple position +pub trait VariableSolutionIndex { + fn index(self, solution: &QuerySolution) -> Option; +} + +impl VariableSolutionIndex for usize { + fn index(self, _: &QuerySolution) -> Option { + Some(self) + } +} + +impl VariableSolutionIndex for &str { + fn index(self, solution: &QuerySolution) -> Option { + solution.variables.iter().position(|v| v.as_str() == self) + } +} + +impl VariableSolutionIndex for &Variable { + fn index(self, solution: &QuerySolution) -> Option { + solution.variables.iter().position(|v| v == self) + } +} + +impl VariableSolutionIndex for Variable { + fn index(self, solution: &QuerySolution) -> Option { + (&self).index(solution) } } /// A SPARQL query variable +/// +/// ``` +/// use oxigraph::sparql::Variable; +/// +/// assert_eq!( +/// "?foo", +/// Variable::new("foo").to_string() +/// ) +/// ``` #[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] pub struct Variable { name: String, @@ -166,7 +295,7 @@ impl Variable { &self.name } - #[deprecated] + #[deprecated(note = "Please use as_str instead")] pub fn name(&self) -> Result<&str> { Ok(self.as_str()) } diff --git a/lib/src/sparql/xml_results.rs b/lib/src/sparql/xml_results.rs index f4546890..a0097777 100644 --- a/lib/src/sparql/xml_results.rs +++ b/lib/src/sparql/xml_results.rs @@ -35,63 +35,58 @@ pub fn write_xml_results(results: QueryResult<'_>, sink: W) -> Result< writer.write_event(Event::End(BytesEnd::borrowed(b"boolean")))?; writer.write_event(Event::End(BytesEnd::borrowed(b"sparql")))?; } - QueryResult::Bindings(bindings) => { - let (variables, results) = bindings.destruct(); + QueryResult::Bindings(solutions) => { writer.write_event(Event::Decl(BytesDecl::new(b"1.0", None, None)))?; let mut sparql_open = BytesStart::borrowed_name(b"sparql"); sparql_open.push_attribute(("xmlns", "http://www.w3.org/2005/sparql-results#")); writer.write_event(Event::Start(sparql_open))?; writer.write_event(Event::Start(BytesStart::borrowed_name(b"head")))?; - for variable in &variables { + for variable in solutions.variables() { let mut variable_tag = BytesStart::borrowed_name(b"variable"); variable_tag.push_attribute(("name", variable.as_str())); writer.write_event(Event::Empty(variable_tag))?; } writer.write_event(Event::End(BytesEnd::borrowed(b"head")))?; writer.write_event(Event::Start(BytesStart::borrowed_name(b"results")))?; - for result in results { - let result = result?; + for solution in solutions { + let solution = solution?; writer.write_event(Event::Start(BytesStart::borrowed_name(b"result")))?; - for (i, value) in result.into_iter().enumerate() { - if let Some(term) = value { - let mut binding_tag = BytesStart::borrowed_name(b"binding"); - binding_tag.push_attribute(("name", variables[i].as_str())); - writer.write_event(Event::Start(binding_tag))?; - match term { - Term::NamedNode(uri) => { - writer - .write_event(Event::Start(BytesStart::borrowed_name(b"uri")))?; - writer.write_event(Event::Text(BytesText::from_plain_str( - uri.as_str(), - )))?; - writer.write_event(Event::End(BytesEnd::borrowed(b"uri")))?; - } - Term::BlankNode(bnode) => { - writer.write_event(Event::Start(BytesStart::borrowed_name( - b"bnode", - )))?; - writer.write_event(Event::Text(BytesText::from_plain_str( - bnode.as_str(), - )))?; - writer.write_event(Event::End(BytesEnd::borrowed(b"bnode")))?; - } - Term::Literal(literal) => { - let mut literal_tag = BytesStart::borrowed_name(b"literal"); - if let Some(language) = literal.language() { - literal_tag.push_attribute(("xml:lang", language)); - } else if !literal.is_plain() { - literal_tag - .push_attribute(("datatype", literal.datatype().as_str())); - } - writer.write_event(Event::Start(literal_tag))?; - writer.write_event(Event::Text(BytesText::from_plain_str( - literal.value(), - )))?; - writer.write_event(Event::End(BytesEnd::borrowed(b"literal")))?; + for (variable, value) in solution.iter() { + let mut binding_tag = BytesStart::borrowed_name(b"binding"); + binding_tag.push_attribute(("name", variable.as_str())); + writer.write_event(Event::Start(binding_tag))?; + match value { + Term::NamedNode(uri) => { + writer.write_event(Event::Start(BytesStart::borrowed_name(b"uri")))?; + writer.write_event(Event::Text(BytesText::from_plain_str( + uri.as_str(), + )))?; + writer.write_event(Event::End(BytesEnd::borrowed(b"uri")))?; + } + Term::BlankNode(bnode) => { + writer + .write_event(Event::Start(BytesStart::borrowed_name(b"bnode")))?; + writer.write_event(Event::Text(BytesText::from_plain_str( + bnode.as_str(), + )))?; + writer.write_event(Event::End(BytesEnd::borrowed(b"bnode")))?; + } + Term::Literal(literal) => { + let mut literal_tag = BytesStart::borrowed_name(b"literal"); + if let Some(language) = literal.language() { + literal_tag.push_attribute(("xml:lang", language)); + } else if !literal.is_plain() { + literal_tag + .push_attribute(("datatype", literal.datatype().as_str())); } + writer.write_event(Event::Start(literal_tag))?; + writer.write_event(Event::Text(BytesText::from_plain_str( + literal.value(), + )))?; + writer.write_event(Event::End(BytesEnd::borrowed(b"literal")))?; } - writer.write_event(Event::End(BytesEnd::borrowed(b"binding")))?; } + writer.write_event(Event::End(BytesEnd::borrowed(b"binding")))?; } writer.write_event(Event::End(BytesEnd::borrowed(b"result")))?; } @@ -175,7 +170,7 @@ pub fn read_xml_results<'a>(source: impl BufRead + 'a) -> Result for (i,var) in variables.iter().enumerate() { mapping.insert(var.as_bytes().to_vec(), i); } - return Ok(QueryResult::Bindings(BindingsIterator::new( + return Ok(QueryResult::Bindings(QuerySolutionsIterator::new( variables.into_iter().map(Variable::new).collect(), Box::new(ResultsIterator { reader, @@ -214,7 +209,7 @@ pub fn read_xml_results<'a>(source: impl BufRead + 'a) -> Result }, State::AfterHead => { if event.name() == b"results" { - return Ok(QueryResult::Bindings(BindingsIterator::new( + return Ok(QueryResult::Bindings(QuerySolutionsIterator::new( variables.into_iter().map(Variable::new).collect(), Box::new(empty()), ))) diff --git a/lib/src/store/memory.rs b/lib/src/store/memory.rs index 5600c9bd..3eb296ec 100644 --- a/lib/src/store/memory.rs +++ b/lib/src/store/memory.rs @@ -31,9 +31,8 @@ use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; /// /// // SPARQL query /// let prepared_query = store.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default())?; -/// let results = prepared_query.exec()?; -/// if let QueryResult::Bindings(results) = results { -/// assert_eq!(results.into_values_iter().next().unwrap()?[0], Some(ex.into())); +/// if let QueryResult::Bindings(mut solutions) = prepared_query.exec()? { +/// assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into())); /// } /// # Result::Ok(()) /// ``` @@ -88,9 +87,8 @@ impl MemoryStore { /// /// // SPARQL query /// let prepared_query = store.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default())?; - /// let results = prepared_query.exec()?; - /// if let QueryResult::Bindings(results) = results { - /// assert_eq!(results.into_values_iter().next().unwrap()?[0], Some(ex.into())); + /// if let QueryResult::Bindings(mut solutions) = prepared_query.exec()? { + /// assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into())); /// } /// # Result::Ok(()) /// ``` diff --git a/lib/src/store/rocksdb.rs b/lib/src/store/rocksdb.rs index 1aea566d..b45fb2fc 100644 --- a/lib/src/store/rocksdb.rs +++ b/lib/src/store/rocksdb.rs @@ -37,8 +37,8 @@ use std::sync::Arc; /// // SPARQL query /// let prepared_query = store.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default())?; /// let results = prepared_query.exec()?; -/// if let QueryResult::Bindings(results) = results { -/// assert_eq!(results.into_values_iter().next().unwrap()?[0], Some(ex.into())); +/// if let QueryResult::Bindings(mut solutions) = results { +/// assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into())); /// } /// # /// # } diff --git a/lib/src/store/sled.rs b/lib/src/store/sled.rs index e613b4ff..d41f477a 100644 --- a/lib/src/store/sled.rs +++ b/lib/src/store/sled.rs @@ -36,9 +36,8 @@ use std::str; /// /// // SPARQL query /// let prepared_query = store.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default())?; -/// let results = prepared_query.exec()?; -/// if let QueryResult::Bindings(results) = results { -/// assert_eq!(results.into_values_iter().next().unwrap()?[0], Some(ex.into())); +/// if let QueryResult::Bindings(mut solutions) = prepared_query.exec()? { +/// assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into())); /// } /// # /// # } diff --git a/lib/tests/service_test_cases.rs b/lib/tests/service_test_cases.rs index f5a13d5c..057ac038 100644 --- a/lib/tests/service_test_cases.rs +++ b/lib/tests/service_test_cases.rs @@ -11,7 +11,7 @@ fn simple_service_test() { &'a self, _: &NamedNode, graph_pattern: &'a GraphPattern, - ) -> Result> { + ) -> Result> { let triples = b" .".as_ref(); do_pattern(triples, graph_pattern, QueryOptions::default()) @@ -30,16 +30,16 @@ fn simple_service_test() { .to_string(); let options = QueryOptions::default().with_service_handler(TestServiceHandler); - let results = do_query(b"".as_ref(), query, options).unwrap(); - let collected = results - .into_values_iter() - .map(move |b| b.unwrap()) + let collected = do_query(b"".as_ref(), query, options) + .unwrap() + .map(|b| { + b.unwrap() + .iter() + .map(|(_, v)| v.clone()) + .collect::>() + }) .collect::>(); - let solution = vec![vec![ - Some(ex(String::from("s"))), - Some(ex(String::from("p"))), - Some(ex(String::from("o"))), - ]]; + let solution = vec![vec![ex("s"), ex("p"), ex("o")]]; assert_eq!(collected, solution); } @@ -52,7 +52,7 @@ fn two_service_test() { &'a self, named_node: &NamedNode, graph_pattern: &'a GraphPattern, - ) -> Result> { + ) -> Result> { let service1 = NamedNode::parse("http://service1.org").unwrap(); let service2 = NamedNode::parse("http://service2.org").unwrap(); if named_node == &service1 { @@ -93,20 +93,18 @@ fn two_service_test() { .to_string(); let options = QueryOptions::default().with_service_handler(TwoServiceTest); - let results = do_query(b"".as_ref(), query, options).unwrap(); - let collected = results - .into_values_iter() - .map(move |b| b.unwrap()) + let collected = do_query(b"".as_ref(), query, options) + .unwrap() + .map(|b| { + b.unwrap() + .iter() + .map(|(_, v)| v.clone()) + .collect::>() + }) .collect::>(); let solution = vec![ - vec![ - Some(literal("Alice".to_string())), - Some(mailto("alice@example.com".to_string())), - ], - vec![ - Some(literal("Bob".to_string())), - Some(mailto("bob@example.com".to_string())), - ], + vec![literal("Alice"), mailto("alice@example.com")], + vec![literal("Bob"), mailto("bob@example.com")], ]; assert_eq!(collected, solution); } @@ -120,7 +118,7 @@ fn silent_service_empty_set_test() { &'a self, _: &NamedNode, _: &'a GraphPattern, - ) -> Result> { + ) -> Result> { Err(Error::msg("This is supposed to fail")) } } @@ -141,12 +139,7 @@ fn silent_service_empty_set_test() { let triples = b"".as_ref(); let options = QueryOptions::default().with_service_handler(ServiceTest); - let results = do_query(triples, query, options).unwrap(); - let collected = results - .into_values_iter() - .map(move |b| b.unwrap()) - .collect::>(); - assert_eq!(collected.len(), 1); + assert_eq!(do_query(triples, query, options).unwrap().count(), 1); } #[test] @@ -158,7 +151,7 @@ fn non_silent_service_test() { &'a self, _: &NamedNode, _: &'a GraphPattern, - ) -> Result> { + ) -> Result> { Err(Error::msg("This is supposed to fail")) } } @@ -179,26 +172,22 @@ fn non_silent_service_test() { let triples = b"".as_ref(); let options = QueryOptions::default().with_service_handler(ServiceTest); - let results = do_query(triples, query, options).unwrap(); - let result = results.into_values_iter().next(); - match result { - Some(Err(_)) => assert_eq!(true, true), - _ => assert_eq!( - true, false, - "This should have been an error since the service fails" - ), + let mut solutions = do_query(triples, query, options).unwrap(); + if let Some(Err(_)) = solutions.next() { + } else { + panic!("This should have been an error since the service fails") } } -fn ex(id: String) -> Term { - Term::NamedNode(NamedNode::parse(format!("http://example.com/{}", &id)).unwrap()) +fn ex(id: &str) -> Term { + Term::NamedNode(NamedNode::parse(format!("http://example.com/{}", id)).unwrap()) } -fn mailto(id: String) -> Term { - Term::NamedNode(NamedNode::parse(format!("mailto:{}", &id)).unwrap()) +fn mailto(id: &str) -> Term { + Term::NamedNode(NamedNode::parse(format!("mailto:{}", id)).unwrap()) } -fn literal(str: String) -> Term { +fn literal(str: &str) -> Term { Term::Literal(Literal::new_simple_literal(str)) } @@ -214,13 +203,13 @@ fn query_store<'a>( store: MemoryStore, query: String, options: QueryOptions<'a>, -) -> Result> { +) -> Result> { match store.prepare_query(&query, options)?.exec()? { QueryResult::Bindings(iterator) => { - let (varaibles, iter) = iterator.destruct(); + let (variables, iter) = iterator.destruct(); let collected = iter.collect::>(); - Ok(BindingsIterator::new( - varaibles, + Ok(QuerySolutionsIterator::new( + variables, Box::new(collected.into_iter()), )) } @@ -232,7 +221,7 @@ fn pattern_store<'a>( store: MemoryStore, pattern: &'a GraphPattern, options: QueryOptions<'a>, -) -> Result> { +) -> Result> { match store .prepare_query_from_pattern(&pattern, options)? .exec()? @@ -240,7 +229,7 @@ fn pattern_store<'a>( QueryResult::Bindings(iterator) => { let (varaibles, iter) = iterator.destruct(); let collected = iter.collect::>(); - Ok(BindingsIterator::new( + Ok(QuerySolutionsIterator::new( varaibles, Box::new(collected.into_iter()), )) @@ -253,7 +242,7 @@ fn do_query<'a>( reader: impl BufRead, query: String, options: QueryOptions<'a>, -) -> Result> { +) -> Result> { let store = make_store(reader)?; query_store(store, query, options) } @@ -262,7 +251,7 @@ fn do_pattern<'a>( reader: impl BufRead, pattern: &'a GraphPattern, options: QueryOptions<'a>, -) -> Result> { +) -> Result> { let store = make_store(reader)?; pattern_store(store, pattern, options) } diff --git a/lib/tests/sparql_test_cases.rs b/lib/tests/sparql_test_cases.rs index 6f211c98..4c857189 100644 --- a/lib/tests/sparql_test_cases.rs +++ b/lib/tests/sparql_test_cases.rs @@ -348,7 +348,7 @@ fn to_graph(result: QueryResult<'_>, with_order: bool) -> Result { )); Ok(graph) } - QueryResult::Bindings(bindings) => { + QueryResult::Bindings(solutions) => { let mut graph = SimpleGraph::default(); let result_set = BlankNode::default(); graph.insert(Triple::new( @@ -356,33 +356,30 @@ fn to_graph(result: QueryResult<'_>, with_order: bool) -> Result { rdf::TYPE.clone(), rs::RESULT_SET.clone(), )); - let (variables, iter) = bindings.destruct(); - for variable in &variables { + for variable in solutions.variables() { graph.insert(Triple::new( result_set, rs::RESULT_VARIABLE.clone(), Literal::new_simple_literal(variable.as_str()), )); } - for (i, binding_values) in iter.enumerate() { - let binding_values = binding_values?; - let solution = BlankNode::default(); - graph.insert(Triple::new(result_set, rs::SOLUTION.clone(), solution)); - for i in 0..variables.len() { - if let Some(ref value) = binding_values[i] { - let binding = BlankNode::default(); - graph.insert(Triple::new(solution, rs::BINDING.clone(), binding)); - graph.insert(Triple::new(binding, rs::VALUE.clone(), value.clone())); - graph.insert(Triple::new( - binding, - rs::VARIABLE.clone(), - Literal::new_simple_literal(variables[i].as_str()), - )); - } + 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)); + 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( + binding, + rs::VARIABLE.clone(), + Literal::new_simple_literal(variable.as_str()), + )); } if with_order { graph.insert(Triple::new( - solution, + solution_id, rs::INDEX.clone(), Literal::from((i + 1) as i128), )); @@ -720,7 +717,7 @@ impl ServiceHandler for StaticServiceHandler { &'a self, service_name: &NamedNode, graph_pattern: &'a GraphPattern, - ) -> Result> { + ) -> Result> { if let QueryResult::Bindings(iterator) = self .services .get(service_name) @@ -731,10 +728,10 @@ impl ServiceHandler for StaticServiceHandler { )? .exec()? { - //TODO: very hugly + //TODO: very ugly let (variables, iter) = iterator.destruct(); let collected = iter.collect::>(); - Ok(BindingsIterator::new( + Ok(QuerySolutionsIterator::new( variables, Box::new(collected.into_iter()), )) diff --git a/lib/tests/wasm.rs b/lib/tests/wasm.rs index cdb82793..0200b932 100644 --- a/lib/tests/wasm.rs +++ b/lib/tests/wasm.rs @@ -22,18 +22,17 @@ mod test { let prepared_query = store .prepare_query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default()) .unwrap(); - let results = prepared_query.exec().unwrap(); - if let QueryResult::Bindings(results) = results { + if let QueryResult::Bindings(mut solutions) = prepared_query.exec().unwrap() { assert_eq!( - results.into_values_iter().next().unwrap().unwrap()[0], - Some(ex.into()) + solutions.next().unwrap().unwrap().get("s"), + Some(&ex.into()) ); } } #[wasm_bindgen_test] fn now() { - if let QueryResult::Bindings(results) = MemoryStore::default() + if let QueryResult::Bindings(solutions) = MemoryStore::default() .prepare_query( "SELECT (YEAR(NOW()) AS ?y) WHERE {}", QueryOptions::default(), @@ -42,8 +41,7 @@ mod test { .exec() .unwrap() { - if let Some(Term::Literal(l)) = &results.into_values_iter().next().unwrap().unwrap()[0] - { + if let Some(Term::Literal(l)) = solutions.next().unwrap().unwrap().get(0) { let year = i64::from_str(l.value()).unwrap(); assert!(2020 <= year && year <= 2100); }