Merge pull request #12 from Tpt/service-improvements

Service support improvements
pull/14/head
Thomas Tanon 5 years ago committed by GitHub
commit 33ecea643c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      lib/src/lib.rs
  2. 26
      lib/src/repository.rs
  3. 638
      lib/src/sparql/eval.rs
  4. 79
      lib/src/sparql/mod.rs
  5. 11
      lib/src/sparql/plan.rs
  6. 7
      lib/src/sparql/plan_builder.rs
  7. 5
      lib/src/store/memory.rs
  8. 18
      lib/src/store/mod.rs
  9. 5
      lib/src/store/rocksdb.rs
  10. 34
      lib/tests/service_test_cases.rs
  11. 4
      lib/tests/sparql_test_cases.rs
  12. 5
      server/src/main.rs

@ -27,9 +27,8 @@
//! assert_eq!(vec![quad], results.unwrap());
//!
//! // SPARQL query
//! let prepared_query = connection.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", None).unwrap();
//! let options = QueryOptions::default();
//! let results = prepared_query.exec(&options).unwrap();
//! let prepared_query = connection.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default()).unwrap();
//! let results = prepared_query.exec().unwrap();
//! if let QueryResult::Bindings(results) = results {
//! assert_eq!(results.into_values_iter().next().unwrap().unwrap()[0], Some(ex.into()));
//! }

@ -1,5 +1,5 @@
use crate::model::*;
use crate::sparql::{GraphPattern, PreparedQuery};
use crate::sparql::{GraphPattern, PreparedQuery, QueryOptions};
use crate::{DatasetSyntax, GraphSyntax, Result};
use std::io::BufRead;
@ -30,9 +30,8 @@ use std::io::BufRead;
/// assert_eq!(vec![quad], results.unwrap());
///
/// // SPARQL query
/// let prepared_query = connection.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", None).unwrap();
/// let options = QueryOptions::default();
/// let results = prepared_query.exec(&options).unwrap();
/// let prepared_query = connection.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default()).unwrap();
/// let results = prepared_query.exec().unwrap();
/// if let QueryResult::Bindings(results) = results {
/// assert_eq!(results.into_values_iter().next().unwrap().unwrap()[0], Some(ex.into()));
/// }
@ -76,24 +75,19 @@ pub trait RepositoryConnection: Clone {
/// connection.insert(&Quad::new(ex.clone(), ex.clone(), ex.clone(), None));
///
/// // SPARQL query
/// let prepared_query = connection.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", None).unwrap();
/// let options = QueryOptions::default();
/// let results = prepared_query.exec(&options).unwrap();
/// let prepared_query = connection.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default()).unwrap();
/// let results = prepared_query.exec().unwrap();
/// if let QueryResult::Bindings(results) = results {
/// assert_eq!(results.into_values_iter().next().unwrap().unwrap()[0], Some(ex.into()));
/// }
/// ```
fn prepare_query<'a>(
&'a self,
query: &str,
base_iri: Option<&'a str>,
) -> Result<Self::PreparedQuery>;
fn prepare_query(&self, query: &str, options: QueryOptions) -> Result<Self::PreparedQuery>;
/// This is similar to `prepare_query`, but useful if a SPARQL query has already been parsed, which is the case when building `ServiceHandler`s for federated queries with `SERVICE` clauses. For examples, look in the tests.
fn prepare_query_from_pattern<'a>(
&'a self,
graph_pattern: &'a GraphPattern,
base_iri: Option<&str>,
fn prepare_query_from_pattern(
&self,
graph_pattern: &GraphPattern,
options: QueryOptions,
) -> Result<Self::PreparedQuery>;
/// Retrieves quads with a filter on each quad component

File diff suppressed because it is too large Load Diff

@ -18,7 +18,6 @@ use crate::sparql::plan::{DatasetView, PlanNode};
use crate::sparql::plan_builder::PlanBuilder;
use crate::store::StoreConnection;
use crate::Result;
use failure::format_err;
use rio_api::iri::Iri;
use std::fmt;
@ -31,7 +30,7 @@ pub use crate::sparql::model::Variable;
/// A prepared [SPARQL query](https://www.w3.org/TR/sparql11-query/)
pub trait PreparedQuery {
/// Evaluates the query and returns its results
fn exec<'a>(&'a self, options: &'a QueryOptions<'a>) -> Result<QueryResult<'a>>;
fn exec(&self) -> Result<QueryResult>;
}
/// An implementation of `PreparedQuery` for internal use
@ -59,10 +58,9 @@ enum SimplePreparedQueryAction<S: StoreConnection> {
}
impl<'a, S: StoreConnection + 'a> SimplePreparedQuery<S> {
pub(crate) fn new(connection: S, query: &str, base_iri: Option<&'a str>) -> Result<Self> {
let dataset = DatasetView::new(connection);
//TODO avoid inserting terms in the Repository StringStore
Ok(Self(match read_sparql_query(query, base_iri)? {
pub(crate) fn new(connection: S, query: &str, options: QueryOptions) -> Result<Self> {
let dataset = DatasetView::new(connection, options.default_graph_as_union);
Ok(Self(match read_sparql_query(query, options.base_iri)? {
QueryVariants::Select {
algebra,
dataset: _,
@ -72,7 +70,7 @@ impl<'a, S: StoreConnection + 'a> SimplePreparedQuery<S> {
SimplePreparedQueryAction::Select {
plan,
variables,
evaluator: SimpleEvaluator::new(dataset, base_iri),
evaluator: SimpleEvaluator::new(dataset, base_iri, options.service_handler),
}
}
QueryVariants::Ask {
@ -83,7 +81,7 @@ impl<'a, S: StoreConnection + 'a> SimplePreparedQuery<S> {
let (plan, _) = PlanBuilder::build(dataset.encoder(), &algebra)?;
SimplePreparedQueryAction::Ask {
plan,
evaluator: SimpleEvaluator::new(dataset, base_iri),
evaluator: SimpleEvaluator::new(dataset, base_iri, options.service_handler),
}
}
QueryVariants::Construct {
@ -100,7 +98,7 @@ impl<'a, S: StoreConnection + 'a> SimplePreparedQuery<S> {
&construct,
variables,
)?,
evaluator: SimpleEvaluator::new(dataset, base_iri),
evaluator: SimpleEvaluator::new(dataset, base_iri, options.service_handler),
}
}
QueryVariants::Describe {
@ -111,7 +109,7 @@ impl<'a, S: StoreConnection + 'a> SimplePreparedQuery<S> {
let (plan, _) = PlanBuilder::build(dataset.encoder(), &algebra)?;
SimplePreparedQueryAction::Describe {
plan,
evaluator: SimpleEvaluator::new(dataset, base_iri),
evaluator: SimpleEvaluator::new(dataset, base_iri, options.service_handler),
}
}
}))
@ -121,62 +119,77 @@ impl<'a, S: StoreConnection + 'a> SimplePreparedQuery<S> {
pub(crate) fn new_from_pattern(
connection: S,
pattern: &GraphPattern,
base_iri: Option<&'a str>,
options: QueryOptions,
) -> Result<Self> {
let dataset = DatasetView::new(connection);
let dataset = DatasetView::new(connection, options.default_graph_as_union);
let (plan, variables) = PlanBuilder::build(dataset.encoder(), pattern)?;
let base_iri = base_iri.map(|str_iri| Iri::parse(str_iri.to_string()));
match base_iri {
Some(Err(_)) => Err(format_err!("Failed to parse base_iri")),
Some(Ok(base_iri)) => Ok(Self(SimplePreparedQueryAction::Select {
let base_iri = if let Some(base_iri) = options.base_iri {
Some(Iri::parse(base_iri.to_string())?)
} else {
None
};
Ok(Self(SimplePreparedQueryAction::Select {
plan,
variables,
evaluator: SimpleEvaluator::new(dataset, Some(base_iri)),
})),
None => Ok(Self(SimplePreparedQueryAction::Select {
plan,
variables,
evaluator: SimpleEvaluator::new(dataset, None),
})),
}
evaluator: SimpleEvaluator::new(dataset, base_iri, options.service_handler),
}))
}
}
impl<S: StoreConnection> PreparedQuery for SimplePreparedQuery<S> {
fn exec<'a>(&'a self, options: &'a QueryOptions<'a>) -> Result<QueryResult<'a>> {
fn exec(&self) -> Result<QueryResult> {
match &self.0 {
SimplePreparedQueryAction::Select {
plan,
variables,
evaluator,
} => evaluator.evaluate_select_plan(&plan, &variables, options),
} => evaluator.evaluate_select_plan(&plan, &variables),
SimplePreparedQueryAction::Ask { plan, evaluator } => {
evaluator.evaluate_ask_plan(&plan, options)
evaluator.evaluate_ask_plan(&plan)
}
SimplePreparedQueryAction::Construct {
plan,
construct,
evaluator,
} => evaluator.evaluate_construct_plan(&plan, &construct, &options),
} => evaluator.evaluate_construct_plan(&plan, &construct),
SimplePreparedQueryAction::Describe { plan, evaluator } => {
evaluator.evaluate_describe_plan(&plan, &options)
evaluator.evaluate_describe_plan(&plan)
}
}
}
}
/// Handler for SPARQL SERVICEs.
///
/// Might be used to implement [SPARQL 1.1 Federated Query](https://www.w3.org/TR/sparql11-federated-query/)
pub trait ServiceHandler {
/// Get the handler for a given service identified by a RDF IRI.
///
/// A service is a function that returns an iterator of bindings from a `GraphPattern`.
/// Returns `None` if there is no handler for the service.
fn handle<'a>(
&'a self,
node: NamedNode,
node: &NamedNode,
) -> Option<(fn(GraphPattern) -> Result<BindingsIterator<'a>>)>;
}
#[derive(Default)]
struct EmptyServiceHandler {}
impl ServiceHandler for EmptyServiceHandler {
fn handle(
&self,
_node: &NamedNode,
) -> Option<(fn(GraphPattern) -> Result<BindingsIterator<'static>>)> {
None
}
}
/// Options for SPARQL query parsing and evaluation like the query base IRI
pub struct QueryOptions<'a> {
pub(crate) base_iri: Option<&'a str>,
pub(crate) default_graph_as_union: bool,
pub(crate) service_handler: Option<Box<dyn ServiceHandler>>,
pub(crate) service_handler: Box<dyn ServiceHandler>,
}
impl<'a> Default for QueryOptions<'a> {
@ -184,7 +197,7 @@ impl<'a> Default for QueryOptions<'a> {
Self {
base_iri: None,
default_graph_as_union: false,
service_handler: None as Option<Box<dyn ServiceHandler>>,
service_handler: Box::new(EmptyServiceHandler::default()),
}
}
}
@ -204,7 +217,7 @@ impl<'a> QueryOptions<'a> {
/// Consider the union of all graphs in the repository as the default graph
pub fn with_service_handler(mut self, service_handler: Box<dyn ServiceHandler>) -> Self {
self.service_handler = Some(service_handler);
self.service_handler = service_handler;
self
}
}

@ -22,7 +22,7 @@ pub enum PlanNode {
service_name: PatternValue,
variables: Vec<Variable>,
child: Box<PlanNode>,
graph_pattern: GraphPattern,
graph_pattern: Box<GraphPattern>,
silent: bool,
},
QuadPatternJoin {
@ -175,7 +175,7 @@ impl PlanNode {
PlanNode::HashDeduplicate { child } => child.add_variables(set),
PlanNode::Skip { child, .. } => child.add_variables(set),
PlanNode::Limit { child, .. } => child.add_variables(set),
PlanNode::Project { child: _, mapping } => {
PlanNode::Project { mapping, .. } => {
for i in 0..mapping.len() {
set.insert(i);
}
@ -473,13 +473,15 @@ pub enum TripleTemplateValue {
pub struct DatasetView<S: StoreConnection> {
store: S,
extra: RefCell<MemoryStrStore>,
default_graph_as_union: bool,
}
impl<S: StoreConnection> DatasetView<S> {
pub fn new(store: S) -> Self {
pub fn new(store: S, default_graph_as_union: bool) -> Self {
Self {
store,
extra: RefCell::new(MemoryStrStore::default()),
default_graph_as_union,
}
}
@ -489,7 +491,6 @@ impl<S: StoreConnection> DatasetView<S> {
predicate: Option<EncodedTerm>,
object: Option<EncodedTerm>,
graph_name: Option<EncodedTerm>,
default_graph_as_union: bool,
) -> Box<dyn Iterator<Item = Result<EncodedQuad>> + 'a> {
if graph_name == None {
Box::new(
@ -500,7 +501,7 @@ impl<S: StoreConnection> DatasetView<S> {
Ok(quad) => quad.graph_name != ENCODED_DEFAULT_GRAPH,
}),
)
} else if graph_name == Some(ENCODED_DEFAULT_GRAPH) && default_graph_as_union {
} else if graph_name == Some(ENCODED_DEFAULT_GRAPH) && self.default_graph_as_union {
Box::new(
self.store
.quads_for_pattern(subject, predicate, object, None)

@ -104,13 +104,14 @@ impl<E: Encoder> PlanBuilder<E> {
right: Box::new(self.build_for_graph_pattern(b, variables, graph_name)?),
},
GraphPattern::Service(n, p, s) => {
// Child building should be at the begging in order for `variables` to be filled
let child = self.build_for_graph_pattern(p, variables, graph_name)?;
let service_name = self.pattern_value_from_named_node_or_variable(n, variables)?;
let graph_pattern = *p.clone();
PlanNode::Service {
service_name,
variables: variables.clone(),
child: Box::new(self.build_for_graph_pattern(p, variables, service_name)?),
graph_pattern,
child: Box::new(child),
graph_pattern: Box::new(*p.clone()),
silent: *s,
}
}

@ -29,9 +29,8 @@ use std::sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard};
/// assert_eq!(vec![quad], results.unwrap());
///
/// // SPARQL query
/// let prepared_query = connection.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", None).unwrap();
/// let options = QueryOptions::default();
/// let results = prepared_query.exec(&options).unwrap();
/// let prepared_query = connection.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default()).unwrap();
/// let results = prepared_query.exec().unwrap();
/// if let QueryResult::Bindings(results) = results {
/// assert_eq!(results.into_values_iter().next().unwrap().unwrap()[0], Some(ex.into()));
/// }

@ -11,7 +11,7 @@ pub use crate::store::memory::MemoryRepository;
pub use crate::store::rocksdb::RocksDbRepository;
use crate::model::*;
use crate::sparql::SimplePreparedQuery;
use crate::sparql::{QueryOptions, SimplePreparedQuery};
use crate::store::numeric_encoder::*;
use crate::{DatasetSyntax, GraphSyntax, RepositoryConnection, Result};
use rio_api::parser::{QuadsParser, TriplesParser};
@ -72,12 +72,8 @@ impl<S: StoreConnection> From<S> for StoreRepositoryConnection<S> {
impl<S: StoreConnection> RepositoryConnection for StoreRepositoryConnection<S> {
type PreparedQuery = SimplePreparedQuery<S>;
fn prepare_query<'a>(
&self,
query: &str,
base_iri: Option<&'a str>,
) -> Result<SimplePreparedQuery<S>> {
SimplePreparedQuery::new(self.inner.clone(), query, base_iri) //TODO: avoid clone
fn prepare_query(&self, query: &str, options: QueryOptions) -> Result<SimplePreparedQuery<S>> {
SimplePreparedQuery::new(self.inner.clone(), query, options) //TODO: avoid clone
}
fn quads_for_pattern<'a>(
@ -101,12 +97,12 @@ impl<S: StoreConnection> RepositoryConnection for StoreRepositoryConnection<S> {
)
}
fn prepare_query_from_pattern<'a>(
&'a self,
fn prepare_query_from_pattern(
&self,
pattern: &GraphPattern,
base_iri: Option<&'a str>,
options: QueryOptions,
) -> Result<Self::PreparedQuery> {
SimplePreparedQuery::new_from_pattern(self.inner.clone(), pattern, base_iri)
SimplePreparedQuery::new_from_pattern(self.inner.clone(), pattern, options)
//TODO: avoid clone
}

@ -40,9 +40,8 @@ use std::str;
/// assert_eq!(vec![quad], results.unwrap());
///
/// // SPARQL query
/// let options = QueryOptions::default();
/// let prepared_query = connection.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", None).unwrap();
/// let results = prepared_query.exec(&options).unwrap();
/// let prepared_query = connection.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", QueryOptions::default()).unwrap();
/// let results = prepared_query.exec().unwrap();
/// if let QueryResult::Bindings(results) = results {
/// assert_eq!(results.into_values_iter().next().unwrap().unwrap()[0], Some(ex.into()));
/// }

@ -12,7 +12,7 @@ fn simple_service_test() {
impl ServiceHandler for TestServiceHandler {
fn handle<'a>(
&'a self,
_named_node: NamedNode,
_named_node: &NamedNode,
) -> Option<(fn(GraphPattern) -> Result<BindingsIterator<'a>>)> {
fn pattern_handler<'a>(graph_pattern: GraphPattern) -> Result<BindingsIterator<'a>> {
let triples =
@ -56,13 +56,13 @@ fn two_service_test() {
impl ServiceHandler for TwoServiceTest {
fn handle<'a>(
&'a self,
named_node: NamedNode,
named_node: &NamedNode,
) -> Option<(fn(GraphPattern) -> Result<BindingsIterator<'a>>)> {
let service1 = NamedNode::parse("http://service1.org").unwrap();
let service2 = NamedNode::parse("http://service2.org").unwrap();
if named_node == service1 {
if named_node == &service1 {
Some(TwoServiceTest::handle_service1)
} else if named_node == service2 {
} else if named_node == &service2 {
Some(TwoServiceTest::handle_service2)
} else {
None
@ -133,7 +133,7 @@ fn silent_service_empty_set_test() {
impl ServiceHandler for ServiceTest {
fn handle<'a>(
&'a self,
_named_node: NamedNode,
_named_node: &NamedNode,
) -> Option<(fn(GraphPattern) -> Result<BindingsIterator<'a>>)> {
Some(ServiceTest::handle_service)
}
@ -177,7 +177,7 @@ fn non_silent_service_test() {
impl ServiceHandler for ServiceTest {
fn handle<'a>(
&'a self,
_named_node: NamedNode,
_named_node: &NamedNode,
) -> Option<(fn(GraphPattern) -> Result<BindingsIterator<'a>>)> {
Some(ServiceTest::handle_service)
}
@ -242,10 +242,11 @@ fn query_repository<'a>(
query: String,
options: QueryOptions<'a>,
) -> Result<BindingsIterator<'a>> {
let connection = repository.connection()?;
let prepared_query = connection.prepare_query(&query, None)?;
let result = prepared_query.exec(&options)?;
match result {
match repository
.connection()?
.prepare_query(&query, options)?
.exec()?
{
QueryResult::Bindings(iterator) => {
let (varaibles, iter) = iterator.destruct();
let collected = iter.collect::<Vec<_>>();
@ -265,10 +266,11 @@ fn pattern_repository<'a>(
pattern: GraphPattern,
options: QueryOptions<'a>,
) -> Result<BindingsIterator<'a>> {
let connection = repository.connection()?;
let prepared_query = connection.prepare_query_from_pattern(&pattern, None)?;
let result = prepared_query.exec(&options)?;
match result {
match repository
.connection()?
.prepare_query_from_pattern(&pattern, options)?
.exec()?
{
QueryResult::Bindings(iterator) => {
let (varaibles, iter) = iterator.destruct();
let collected = iter.collect::<Vec<_>>();
@ -277,9 +279,7 @@ fn pattern_repository<'a>(
Box::new(collected.into_iter()),
))
}
_ => Err(format_err!(
"Excpected bindings but got another QueryResult"
)),
_ => Err(format_err!("Expected bindings but got another QueryResult")),
}
}

@ -158,13 +158,13 @@ fn sparql_w3c_query_evaluation_testsuite() -> Result<()> {
}
match repository
.connection()?
.prepare_query(&read_file_to_string(&test.query)?, Some(&test.query))
.prepare_query(&read_file_to_string(&test.query)?, QueryOptions::default().with_base_iri(&test.query))
{
Err(error) => Err(format_err!(
"Failure to parse query of {} with error: {}",
test, error
)),
Ok(query) => match query.exec(&QueryOptions::default()) {
Ok(query) => match query.exec() {
Err(error) => Err(format_err!(
"Failure to execute query of {} with error: {}",
test, error

@ -148,10 +148,9 @@ fn evaluate_sparql_query<R: RepositoryConnection>(
request: &Request,
) -> Response {
//TODO: stream
let options = QueryOptions::default();
match connection.prepare_query(query, None) {
match connection.prepare_query(query, QueryOptions::default()) {
Ok(query) => {
let results = query.exec(&options).unwrap();
let results = query.exec().unwrap();
if let QueryResult::Graph(_) = results {
let supported_formats = [
GraphSyntax::NTriples.media_type(),

Loading…
Cancel
Save