Adds execution of SPARQL Federation test suite

TODO: Make service5 tests pass
(service name from an other part of the SPARQL query)
pull/21/head
Tpt 5 years ago
parent 96837fb64d
commit aac2c0ba87
  1. 15
      lib/src/sparql/xml_results.rs
  2. 152
      lib/tests/sparql_test_cases.rs

@ -401,17 +401,10 @@ impl<R: BufRead> ResultsIterator<R> {
State::Start => state = State::End, State::Start => state = State::End,
State::Result => return Ok(Some(new_bindings)), State::Result => return Ok(Some(new_bindings)),
State::Binding => { State::Binding => {
match (&current_var, &term) { if let Some(var) = &current_var {
(Some(var), Some(term)) => { new_bindings[self.mapping[var]] = term.clone()
new_bindings[self.mapping[var]] = Some(term.clone()) } else {
} return Err(format_err!("No name found for <binding> tag"));
(Some(var), None) => {
return Err(format_err!(
"No variable found for variable {}",
self.reader.decode(&var)?
));
}
_ => return Err(format_err!("No name found for <binding> tag")),
} }
term = None; term = None;
state = State::Result; state = State::Result;

@ -4,14 +4,15 @@ use rayon::prelude::*;
use rudf::model::vocab::rdf; use rudf::model::vocab::rdf;
use rudf::model::vocab::rdfs; use rudf::model::vocab::rdfs;
use rudf::model::*; use rudf::model::*;
use rudf::sparql::{PreparedQuery, QueryOptions}; use rudf::sparql::*;
use rudf::sparql::{Query, QueryResult, QueryResultSyntax}; use rudf::*;
use rudf::{GraphSyntax, MemoryRepository, Repository, RepositoryConnection, Result}; use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc;
#[test] #[test]
fn sparql_w3c_syntax_testsuite() -> Result<()> { fn sparql_w3c_syntax_testsuite() -> Result<()> {
@ -97,6 +98,7 @@ fn sparql_w3c_query_evaluation_testsuite() -> Result<()> {
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/negation/manifest.ttl", "http://www.w3.org/2009/sparql/docs/tests/data-sparql11/negation/manifest.ttl",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/project-expression/manifest.ttl", "http://www.w3.org/2009/sparql/docs/tests/data-sparql11/project-expression/manifest.ttl",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/property-path/manifest.ttl", "http://www.w3.org/2009/sparql/docs/tests/data-sparql11/property-path/manifest.ttl",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/service/manifest.ttl",
"http://www.w3.org/2009/sparql/docs/tests/data-sparql11/subquery/manifest.ttl", "http://www.w3.org/2009/sparql/docs/tests/data-sparql11/subquery/manifest.ttl",
]; ];
@ -133,7 +135,10 @@ fn sparql_w3c_query_evaluation_testsuite() -> Result<()> {
NamedNode::parse("http://www.w3.org/2009/sparql/docs/tests/data-sparql11/property-path/manifest#pp35").unwrap(), NamedNode::parse("http://www.w3.org/2009/sparql/docs/tests/data-sparql11/property-path/manifest#pp35").unwrap(),
//We write "2"^^xsd:decimal instead of "2.0"^^xsd:decimal //We write "2"^^xsd:decimal instead of "2.0"^^xsd:decimal
NamedNode::parse("http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest#agg-err-02").unwrap(), NamedNode::parse("http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest#agg-err-02").unwrap(),
NamedNode::parse("http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest#agg-avg-02").unwrap() NamedNode::parse("http://www.w3.org/2009/sparql/docs/tests/data-sparql11/aggregates/manifest#agg-avg-02").unwrap(),
//SERVICE name from a BGP
NamedNode::parse("http://www.w3.org/2009/sparql/docs/tests/data-sparql11/service/manifest#service5").unwrap(),
]; ];
let tests: Result<Vec<_>> = manifest_10_urls let tests: Result<Vec<_>> = manifest_10_urls
@ -158,39 +163,39 @@ fn sparql_w3c_query_evaluation_testsuite() -> Result<()> {
} }
match repository match repository
.connection()? .connection()?
.prepare_query(&read_file_to_string(&test.query)?, QueryOptions::default().with_base_iri(&test.query)) .prepare_query(&read_file_to_string(&test.query)?, QueryOptions::default().with_base_iri(&test.query).with_service_handler(StaticServiceHandler::new(&test.service_data)?))
{ {
Err(error) => Err(format_err!( Err(error) => Err(format_err!(
"Failure to parse query of {} with error: {}", "Failure to parse query of {} with error: {}",
test, error test, error
)), )),
Ok(query) => match query.exec() { Ok(query) => match query.exec() {
Err(error) => Err(format_err!( Err(error) => Err(format_err!(
"Failure to execute query of {} with error: {}", "Failure to execute query of {} with error: {}",
test, error test, error
)), )),
Ok(result) => { Ok(result) => {
let expected_graph = let expected_graph =
load_sparql_query_result_graph(test.result.as_ref().unwrap())?; load_sparql_query_result_graph(test.result.as_ref().unwrap()).map_err(|e| format_err!("Error constructing expected graph for {}: {}", test, e))?;
let with_order = expected_graph let with_order = expected_graph
.triples_for_predicate(&rs::INDEX) .triples_for_predicate(&rs::INDEX)
.next() .next()
.is_some(); .is_some();
let actual_graph = to_graph(result, with_order).map_err(|e| format_err!("Error constructing result graph for {}: {}", test, e))?; let actual_graph = to_graph(result, with_order).map_err(|e| format_err!("Error constructing result graph for {}: {}", test, e))?;
if actual_graph.is_isomorphic(&expected_graph) { if actual_graph.is_isomorphic(&expected_graph) {
Ok(()) Ok(())
} else { } else {
Err(format_err!("Failure on {}.\nExpected file:\n{}\nOutput file:\n{}\nParsed query:\n{}\nData:\n{}\n", Err(format_err!("Failure on {}.\nExpected file:\n{}\nOutput file:\n{}\nParsed query:\n{}\nData:\n{}\n",
test, test,
expected_graph, expected_graph,
actual_graph, actual_graph,
Query::parse(&read_file_to_string(&test.query)?, Some(&test.query)).unwrap(), Query::parse(&read_file_to_string(&test.query)?, Some(&test.query)).unwrap(),
repository_to_string(&repository) repository_to_string(&repository)
)) ))
}
} }
} },
}, }
}
} else if test.kind != "NegativeSyntaxTest11" { } else if test.kind != "NegativeSyntaxTest11" {
panic!("Not supported test: {}", test) panic!("Not supported test: {}", test)
} else { } else {
@ -410,6 +415,7 @@ pub struct Test {
pub query: String, pub query: String,
pub data: Option<String>, pub data: Option<String>,
pub graph_data: Vec<String>, pub graph_data: Vec<String>,
pub service_data: Vec<(String, String)>,
pub result: Option<String>, pub result: Option<String>,
} }
@ -488,6 +494,12 @@ pub mod qt {
pub static ref GRAPH_DATA: NamedNode = pub static ref GRAPH_DATA: NamedNode =
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#graphData") NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#graphData")
.unwrap(); .unwrap();
pub static ref SERVICE_DATA: NamedNode =
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#serviceData")
.unwrap();
pub static ref ENDPOINT: NamedNode =
NamedNode::parse("http://www.w3.org/2001/sw/DataAccess/tests/test-query#endpoint")
.unwrap();
} }
} }
@ -522,31 +534,53 @@ impl Iterator for TestManifest {
Some(Term::Literal(c)) => Some(c.value().to_string()), Some(Term::Literal(c)) => Some(c.value().to_string()),
_ => None, _ => None,
}; };
let (query, data, graph_data) = match self let (query, data, graph_data, service_data) = match self
.graph .graph
.object_for_subject_predicate(&test_subject, &*mf::ACTION) .object_for_subject_predicate(&test_subject, &*mf::ACTION)
{ {
Some(Term::NamedNode(n)) => (n.as_str().to_string(), None, vec![]), Some(Term::NamedNode(n)) => (n.as_str().to_owned(), None, vec![], vec![]),
Some(Term::BlankNode(n)) => { Some(Term::BlankNode(n)) => {
let n = n.clone().into(); let n = n.clone().into();
let query = match self.graph.object_for_subject_predicate(&n, &qt::QUERY) { let query = match self.graph.object_for_subject_predicate(&n, &qt::QUERY) {
Some(Term::NamedNode(q)) => q.as_str().to_string(), Some(Term::NamedNode(q)) => q.as_str().to_owned(),
Some(_) => return Some(Err(format_err!("invalid query"))), Some(_) => return Some(Err(format_err!("invalid query"))),
None => return Some(Err(format_err!("query not found"))), None => return Some(Err(format_err!("query not found"))),
}; };
let data = match self.graph.object_for_subject_predicate(&n, &qt::DATA) { let data = match self.graph.object_for_subject_predicate(&n, &qt::DATA) {
Some(Term::NamedNode(q)) => Some(q.as_str().to_string()), Some(Term::NamedNode(q)) => Some(q.as_str().to_owned()),
_ => None, _ => None,
}; };
let graph_data = self let graph_data = self
.graph .graph
.objects_for_subject_predicate(&n, &qt::GRAPH_DATA) .objects_for_subject_predicate(&n, &qt::GRAPH_DATA)
.filter_map(|g| match g { .filter_map(|g| match g {
Term::NamedNode(q) => Some(q.as_str().to_string()), 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, _ => 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(); .collect();
(query, data, graph_data) (query, data, graph_data, service_data)
} }
Some(_) => return Some(Err(format_err!("invalid action"))), Some(_) => return Some(Err(format_err!("invalid action"))),
None => { None => {
@ -560,7 +594,7 @@ impl Iterator for TestManifest {
.graph .graph
.object_for_subject_predicate(&test_subject, &*mf::RESULT) .object_for_subject_predicate(&test_subject, &*mf::RESULT)
{ {
Some(Term::NamedNode(n)) => Some(n.as_str().to_string()), Some(Term::NamedNode(n)) => Some(n.as_str().to_owned()),
Some(_) => return Some(Err(format_err!("invalid result"))), Some(_) => return Some(Err(format_err!("invalid result"))),
None => None, None => None,
}; };
@ -572,6 +606,7 @@ impl Iterator for TestManifest {
query, query,
data, data,
graph_data, graph_data,
service_data,
result, result,
})) }))
} }
@ -595,7 +630,7 @@ impl Iterator for TestManifest {
self.manifests_to_do.extend( self.manifests_to_do.extend(
RdfListIterator::iter(&self.graph, list.clone().into()) RdfListIterator::iter(&self.graph, list.clone().into())
.filter_map(|m| match m { .filter_map(|m| match m {
Term::NamedNode(nm) => Some(nm.as_str().to_string()), Term::NamedNode(nm) => Some(nm.into_string()),
_ => None, _ => None,
}), }),
); );
@ -670,3 +705,56 @@ impl<'a> Iterator for RdfListIterator<'a> {
} }
} }
} }
#[derive(Clone)]
struct StaticServiceHandler {
services: Arc<HashMap<NamedNode, MemoryRepository>>,
}
impl StaticServiceHandler {
fn new(services: &[(String, String)]) -> Result<Self> {
Ok(Self {
services: Arc::new(
services
.iter()
.map(|(name, data)| {
let name = NamedNode::parse(name)?;
let repository = MemoryRepository::default();
load_graph_to_repository(&data, &mut repository.connection()?, None)?;
Ok((name, repository))
})
.collect::<Result<_>>()?,
),
})
}
}
impl ServiceHandler for StaticServiceHandler {
fn handle<'a>(
&'a self,
service_name: &NamedNode,
graph_pattern: &'a GraphPattern,
) -> Result<BindingsIterator<'a>> {
if let QueryResult::Bindings(iterator) = self
.services
.get(service_name)
.ok_or_else(|| format_err!("Service {} not found", service_name))?
.connection()?
.prepare_query_from_pattern(
&graph_pattern,
QueryOptions::default().with_service_handler(self.clone()),
)?
.exec()?
{
//TODO: very hugly
let (variables, iter) = iterator.destruct();
let collected = iter.collect::<Vec<_>>();
Ok(BindingsIterator::new(
variables,
Box::new(collected.into_iter()),
))
} else {
Err(format_err!("Expected bindings but got another QueryResult"))
}
}
}

Loading…
Cancel
Save