diff --git a/lib/src/lib.rs b/lib/src/lib.rs index c0e5bc9b..69dba29e 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -11,7 +11,7 @@ //! ``` //! use rudf::model::*; //! use rudf::{Repository, RepositoryConnection, MemoryRepository, Result}; -//! use crate::rudf::sparql::PreparedQuery; +//! use crate::rudf::sparql::{PreparedQuery, QueryOptions}; //! use rudf::sparql::QueryResult; //! //! let repository = MemoryRepository::default(); @@ -27,7 +27,7 @@ //! assert_eq!(vec![quad], results.unwrap()); //! //! // SPARQL query -//! let prepared_query = connection.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", None).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())); diff --git a/lib/src/repository.rs b/lib/src/repository.rs index e1f9dde7..7d1aca4d 100644 --- a/lib/src/repository.rs +++ b/lib/src/repository.rs @@ -1,5 +1,5 @@ use crate::model::*; -use crate::sparql::PreparedQuery; +use crate::sparql::{PreparedQuery, QueryOptions}; use crate::{DatasetSyntax, GraphSyntax, Result}; use std::io::BufRead; @@ -14,7 +14,7 @@ use std::io::BufRead; /// ``` /// use rudf::model::*; /// use rudf::{Repository, RepositoryConnection, MemoryRepository, Result}; -/// use crate::rudf::sparql::PreparedQuery; +/// use crate::rudf::sparql::{PreparedQuery, QueryOptions}; /// use rudf::sparql::QueryResult; /// /// let repository = MemoryRepository::default(); @@ -30,7 +30,7 @@ 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 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())); @@ -64,7 +64,7 @@ pub trait RepositoryConnection: Clone { /// ``` /// use rudf::model::*; /// use rudf::{Repository, RepositoryConnection, MemoryRepository}; - /// use rudf::sparql::PreparedQuery; + /// use rudf::sparql::{PreparedQuery, QueryOptions}; /// use rudf::sparql::QueryResult; /// /// let repository = MemoryRepository::default(); @@ -75,13 +75,13 @@ 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 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(&self, query: &str, base_iri: Option<&str>) -> Result; + fn prepare_query(&self, query: &str, options: QueryOptions) -> Result; /// Retrieves quads with a filter on each quad component /// diff --git a/lib/src/sparql/mod.rs b/lib/src/sparql/mod.rs index a48a4244..dbbbdb6c 100644 --- a/lib/src/sparql/mod.rs +++ b/lib/src/sparql/mod.rs @@ -31,9 +31,9 @@ pub trait PreparedQuery { } /// An implementation of `PreparedQuery` for internal use -pub struct SimplePreparedQuery(SimplePreparedQueryOptions); +pub struct SimplePreparedQuery(SimplePreparedQueryAction); -enum SimplePreparedQueryOptions { +enum SimplePreparedQueryAction { Select { plan: PlanNode, variables: Vec, @@ -55,17 +55,17 @@ enum SimplePreparedQueryOptions { } impl SimplePreparedQuery { - pub(crate) fn new(connection: S, query: &str, base_iri: Option<&str>) -> Result { - let dataset = DatasetView::new(connection); + pub(crate) fn new(connection: S, query: &str, options: QueryOptions) -> Result { + let dataset = DatasetView::new(connection, options.default_graph_as_union); //TODO avoid inserting terms in the Repository StringStore - Ok(Self(match read_sparql_query(query, base_iri)? { + Ok(Self(match read_sparql_query(query, options.base_iri)? { QueryVariants::Select { algebra, dataset: _, base_iri, } => { let (plan, variables) = PlanBuilder::build(dataset.encoder(), &algebra)?; - SimplePreparedQueryOptions::Select { + SimplePreparedQueryAction::Select { plan, variables, evaluator: SimpleEvaluator::new(dataset, base_iri), @@ -77,7 +77,7 @@ impl SimplePreparedQuery { base_iri, } => { let (plan, _) = PlanBuilder::build(dataset.encoder(), &algebra)?; - SimplePreparedQueryOptions::Ask { + SimplePreparedQueryAction::Ask { plan, evaluator: SimpleEvaluator::new(dataset, base_iri), } @@ -89,7 +89,7 @@ impl SimplePreparedQuery { base_iri, } => { let (plan, variables) = PlanBuilder::build(dataset.encoder(), &algebra)?; - SimplePreparedQueryOptions::Construct { + SimplePreparedQueryAction::Construct { plan, construct: PlanBuilder::build_graph_template( dataset.encoder(), @@ -105,7 +105,7 @@ impl SimplePreparedQuery { base_iri, } => { let (plan, _) = PlanBuilder::build(dataset.encoder(), &algebra)?; - SimplePreparedQueryOptions::Describe { + SimplePreparedQueryAction::Describe { plan, evaluator: SimpleEvaluator::new(dataset, base_iri), } @@ -117,26 +117,55 @@ impl SimplePreparedQuery { impl PreparedQuery for SimplePreparedQuery { fn exec(&self) -> Result> { match &self.0 { - SimplePreparedQueryOptions::Select { + SimplePreparedQueryAction::Select { plan, variables, evaluator, } => evaluator.evaluate_select_plan(&plan, &variables), - SimplePreparedQueryOptions::Ask { plan, evaluator } => { + SimplePreparedQueryAction::Ask { plan, evaluator } => { evaluator.evaluate_ask_plan(&plan) } - SimplePreparedQueryOptions::Construct { + SimplePreparedQueryAction::Construct { plan, construct, evaluator, } => evaluator.evaluate_construct_plan(&plan, &construct), - SimplePreparedQueryOptions::Describe { plan, evaluator } => { + SimplePreparedQueryAction::Describe { plan, evaluator } => { evaluator.evaluate_describe_plan(&plan) } } } } +/// 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, +} + +impl<'a> Default for QueryOptions<'a> { + fn default() -> Self { + Self { + base_iri: None, + default_graph_as_union: false, + } + } +} + +impl<'a> QueryOptions<'a> { + /// Allows to set the base IRI of the query + pub fn with_base_iri(mut self, base_iri: &'a str) -> Self { + self.base_iri = Some(base_iri); + self + } + + /// Consider the union of all graphs in the repository as the default graph + pub fn with_default_graph_as_union(mut self) -> Self { + self.default_graph_as_union = true; + self + } +} + /// A parsed [SPARQL query](https://www.w3.org/TR/sparql11-query/) #[derive(Eq, PartialEq, Debug, Clone, Hash)] pub struct Query(QueryVariants); diff --git a/lib/src/sparql/plan.rs b/lib/src/sparql/plan.rs index 370746f1..88d1a912 100644 --- a/lib/src/sparql/plan.rs +++ b/lib/src/sparql/plan.rs @@ -1,6 +1,7 @@ use crate::sparql::eval::StringOrStoreString; use crate::store::numeric_encoder::{ EncodedQuad, EncodedTerm, Encoder, MemoryStrStore, StrContainer, StrLookup, + ENCODED_DEFAULT_GRAPH, }; use crate::store::StoreConnection; use crate::Result; @@ -462,13 +463,15 @@ pub enum TripleTemplateValue { pub struct DatasetView { store: S, extra: RefCell, + default_graph_as_union: bool, } impl DatasetView { - 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, } } @@ -479,8 +482,33 @@ impl DatasetView { object: Option, graph_name: Option, ) -> Box> + 'a> { - self.store - .quads_for_pattern(subject, predicate, object, graph_name) + if graph_name == None { + Box::new( + self.store + .quads_for_pattern(subject, predicate, object, None) + .filter(|quad| match quad { + Err(_) => true, + Ok(quad) => quad.graph_name != ENCODED_DEFAULT_GRAPH, + }), + ) + } else if graph_name == Some(ENCODED_DEFAULT_GRAPH) && self.default_graph_as_union { + Box::new( + self.store + .quads_for_pattern(subject, predicate, object, None) + .map(|quad| { + let quad = quad?; + Ok(EncodedQuad::new( + quad.subject, + quad.predicate, + quad.object, + ENCODED_DEFAULT_GRAPH, + )) + }), + ) + } else { + self.store + .quads_for_pattern(subject, predicate, object, graph_name) + } } pub fn encoder<'a>(&'a self) -> impl Encoder + StrContainer + 'a { diff --git a/lib/src/store/memory.rs b/lib/src/store/memory.rs index b0aaecb2..d9cbe5cd 100644 --- a/lib/src/store/memory.rs +++ b/lib/src/store/memory.rs @@ -14,7 +14,7 @@ use std::sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard}; /// use rudf::model::*; /// use rudf::{Repository, RepositoryConnection, MemoryRepository, Result}; /// use crate::rudf::sparql::PreparedQuery; -/// use rudf::sparql::QueryResult; +/// use rudf::sparql::{QueryResult, QueryOptions}; /// /// let repository = MemoryRepository::default(); /// let mut connection = repository.connection().unwrap(); @@ -29,7 +29,7 @@ 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 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())); diff --git a/lib/src/store/mod.rs b/lib/src/store/mod.rs index 7b1a0260..e648a824 100644 --- a/lib/src/store/mod.rs +++ b/lib/src/store/mod.rs @@ -10,7 +10,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}; @@ -71,8 +71,8 @@ impl From for StoreRepositoryConnection { impl RepositoryConnection for StoreRepositoryConnection { type PreparedQuery = SimplePreparedQuery; - fn prepare_query(&self, query: &str, base_iri: Option<&str>) -> Result> { - SimplePreparedQuery::new(self.inner.clone(), query, base_iri) //TODO: avoid clone + fn prepare_query(&self, query: &str, options: QueryOptions) -> Result> { + SimplePreparedQuery::new(self.inner.clone(), query, options) //TODO: avoid clone } fn quads_for_pattern<'a>( diff --git a/lib/src/store/rocksdb.rs b/lib/src/store/rocksdb.rs index cb0b79de..416855bf 100644 --- a/lib/src/store/rocksdb.rs +++ b/lib/src/store/rocksdb.rs @@ -24,7 +24,7 @@ use std::str; /// ```ignored /// use rudf::model::*; /// use rudf::{Repository, RepositoryConnection, RocksDbRepository, Result}; -/// use crate::rudf::sparql::PreparedQuery; +/// use crate::rudf::sparql::{PreparedQuery, QueryOptions}; /// use rudf::sparql::QueryResult; /// /// let repository = RocksDbRepository::open("example.db").unwrap(); @@ -40,7 +40,7 @@ use std::str; /// assert_eq!(vec![quad], results.unwrap()); /// /// // SPARQL query -/// let prepared_query = connection.prepare_query("SELECT ?s WHERE { ?s ?p ?o }", None).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())); diff --git a/lib/tests/sparql_test_cases.rs b/lib/tests/sparql_test_cases.rs index f76dec9d..fcef66c3 100644 --- a/lib/tests/sparql_test_cases.rs +++ b/lib/tests/sparql_test_cases.rs @@ -4,7 +4,7 @@ use rayon::prelude::*; use rudf::model::vocab::rdf; use rudf::model::vocab::rdfs; use rudf::model::*; -use rudf::sparql::PreparedQuery; +use rudf::sparql::{PreparedQuery, QueryOptions}; use rudf::sparql::{Query, QueryResult, QueryResultSyntax}; use rudf::{GraphSyntax, MemoryRepository, Repository, RepositoryConnection, Result}; use std::fmt; @@ -158,7 +158,7 @@ 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: {}", diff --git a/server/src/main.rs b/server/src/main.rs index da7bcea6..3c2cd587 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -4,8 +4,7 @@ use clap::ArgMatches; use rouille::input::priority_header_preferred; use rouille::url::form_urlencoded; use rouille::{content_encoding, start_server, Request, Response}; -use rudf::sparql::QueryResult; -use rudf::sparql::{PreparedQuery, QueryResultSyntax}; +use rudf::sparql::{PreparedQuery, QueryOptions, QueryResult, QueryResultSyntax}; use rudf::{ DatasetSyntax, FileSyntax, GraphSyntax, MemoryRepository, Repository, RepositoryConnection, RocksDbRepository, @@ -149,7 +148,7 @@ fn evaluate_sparql_query( request: &Request, ) -> Response { //TODO: stream - match connection.prepare_query(query, None) { + match connection.prepare_query(query, QueryOptions::default()) { Ok(query) => { let results = query.exec().unwrap(); if let QueryResult::Graph(_) = results {