-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
226 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,5 +39,6 @@ pub mod parser; | |
pub mod prefix; | ||
pub mod quad; | ||
pub mod serializer; | ||
pub mod sparql; | ||
pub mod term; | ||
pub mod triple; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
//! Common traits for working with [SPARQL](https://www.w3.org/TR/sparql11-query/). | ||
//! | ||
//! # Design rationale | ||
//! | ||
//! These traits are deliberately very generic. | ||
//! Specific implementations may have additional features, such as: | ||
//! | ||
//! - preparing a query for multiple use; | ||
//! - setting default values for `BASE`, `PREFIX`, `FROM`, `FROM NAMED` directives, | ||
//! before parsing query string; | ||
//! - pre-binding variables before evaluating query; | ||
//! - etc... | ||
//! | ||
//! However, we do not want to impose these feature, or any subset thereof, | ||
//! to all implementation of Sophia. | ||
|
||
use crate::term::TTerm; | ||
use crate::triple::stream::TripleSource; | ||
use std::borrow::Borrow; | ||
use std::error::Error; | ||
|
||
/// A dataset that can be queried with SPARQL. | ||
pub trait SparqlDataset { | ||
/// The type of terms that SELECT queries will return. | ||
type BindingsTerm: TTerm; | ||
/// The type of bindings that SELECT queries will return. | ||
type BindingsResult: SparqlBindings<Self>; | ||
/// The type of triples that GRAPH and DESCRIBE queries will return. | ||
type TriplesResult: TripleSource; | ||
/// The type of errors that processing SPARQL queries may raise. | ||
type SparqlError: Error + 'static; | ||
/// The type representing pre-processed queries. | ||
/// | ||
/// See [`prepare_query`](#tymethod.prepare_query) for more defail. | ||
type Query: Query<Error=Self::SparqlError>; | ||
|
||
/// Parse and immediately execute `query`. | ||
/// | ||
/// `query` is usually either a `&str` that will be parsed on the fly, | ||
/// or a `Self::Query` that was earlier prepared by the [`prepare_query`] method. | ||
/// | ||
/// [`prepare_query`]: #method.prepared | ||
fn query<Q>(&self, query: Q) -> Result<SparqlResult<Self>, Self::SparqlError> | ||
where Q: ToQuery<Self::Query>; | ||
|
||
/// Prepare a query for multiple future executions. | ||
/// | ||
/// This allows some implementation to mutualize parsing, | ||
/// (or any other pre-processing step) of the query string. | ||
/// There is however no guarantee on how much pre-processing is actually done. | ||
/// | ||
/// # Note to implementers | ||
/// | ||
/// If it is impossible or inconvenient to provide a type for pre-parsed queries, | ||
/// you can still use `String`, which implements the [`Query`] trait. | ||
/// | ||
/// [`Query`]: ./trait.Query.html | ||
fn prepare_query(&self, query_string: &str) ->Result<Self::Query, Self::SparqlError> { | ||
Self::Query::parse(query_string) | ||
} | ||
} | ||
|
||
/// Preprocessed query, ready for execution. | ||
/// | ||
/// This trait exist to allow *some* implementations of [`SparqlDataset`] | ||
/// to mutualize the parsing of queries in the [`prepare_query`] method. | ||
/// | ||
/// [`SparqlDataset`]: ./trait.SparqlDataset.html | ||
/// [`prepare_query`]: ./trait.SparqlDataset.html#tymethod.prepare_query | ||
pub trait Query: Sized { | ||
type Error: Error + 'static; | ||
fn parse(query_source: &str) -> Result<Self, Self::Error>; | ||
} | ||
|
||
impl Query for String { | ||
type Error = std::convert::Infallible; | ||
fn parse(query_source: &str) -> Result<Self, Self::Error> { | ||
Ok(query_source.into()) | ||
} | ||
|
||
} | ||
|
||
/// A utility trait to allow [`SparqlDataset::query`] | ||
/// to accept either `&str` or `Self::Query`. | ||
/// | ||
/// [`SparqlDataset::query`]: ./trait.SparqlDataset.html#tymethod.query | ||
pub trait ToQuery<Q: Query> { | ||
type Out: Borrow<Q>; | ||
fn to_query(self) -> Result<Self::Out, Q::Error>; | ||
} | ||
|
||
impl<'a, Q> ToQuery<Q> for &'a Q | ||
where | ||
Q: Query, | ||
{ | ||
type Out = &'a Q; | ||
fn to_query(self) -> Result<Self::Out, Q::Error> { | ||
Ok(self) | ||
} | ||
} | ||
|
||
impl<'a, Q> ToQuery<Q> for &'a str | ||
where | ||
Q: Query, | ||
{ | ||
type Out = Q; | ||
fn to_query(self) -> Result<Self::Out, Q::Error> { | ||
Q::parse(self) | ||
} | ||
} | ||
|
||
|
||
|
||
/// The result of executing a SPARQL query. | ||
pub enum SparqlResult<T> | ||
where | ||
T: SparqlDataset + ?Sized, | ||
{ | ||
/// The result of a SELECT query | ||
Bindings(T::BindingsResult), | ||
/// The result of an ASK query | ||
Boolean(bool), | ||
/// The result of a CONSTRUCT or DESCRIBE query | ||
Triples(T::TriplesResult), | ||
} | ||
|
||
impl<T> SparqlResult<T> | ||
where | ||
T: SparqlDataset + ?Sized, | ||
{ | ||
/// Get this result as a `Bindings`. | ||
/// | ||
/// # Panics | ||
/// This will panic if `self` is actually of another kind. | ||
pub fn into_bindings(self) -> T::BindingsResult { | ||
match self { | ||
SparqlResult::Bindings(b) => b, | ||
_ => panic!("This SparqlResult is not a Bindings"), | ||
} | ||
} | ||
/// Get this result as a `Boolean`. | ||
/// | ||
/// # Panics | ||
/// This will panic if `self` is actually of another kind. | ||
pub fn into_boolean(self) -> bool { | ||
match self { | ||
SparqlResult::Boolean(b) => b, | ||
_ => panic!("This SparqlResult is not a Boolean"), | ||
} | ||
} | ||
/// Get this result as a `Triples`. | ||
/// | ||
/// # Panics | ||
/// This will panic if `self` is actually of another kind. | ||
pub fn into_triples(self) -> T::TriplesResult { | ||
match self { | ||
SparqlResult::Triples(t) => t, | ||
_ => panic!("This SparqlResult is not a Triples"), | ||
} | ||
} | ||
} | ||
|
||
/// The result of executing a SPARQL SELECT query | ||
pub trait SparqlBindings<D>: | ||
IntoIterator<Item = Result<Vec<Option<D::BindingsTerm>>, D::SparqlError>> | ||
where | ||
D: SparqlDataset + ?Sized, | ||
{ | ||
/// Return the list of SELECTed variable names | ||
fn variables(&self) -> Vec<&str>; | ||
} | ||
|
||
/// A dummy module to check that implementing these traits is actually possible | ||
#[cfg(test)] | ||
mod dummy { | ||
use super::*; | ||
use crate::dataset::Dataset; | ||
use std::convert::Infallible; | ||
|
||
pub type MyTerm = crate::term::test::TestTerm<String>; | ||
pub type MyQuad = ([MyTerm; 3], Option<MyTerm>); | ||
pub type MyDataset = Vec<MyQuad>; | ||
|
||
pub struct MyBindings(Box<dyn Iterator<Item = Result<Vec<Option<MyTerm>>, Infallible>>>); | ||
|
||
impl IntoIterator for MyBindings { | ||
type Item = Result<Vec<Option<MyTerm>>, Infallible>; | ||
type IntoIter = Box<dyn Iterator<Item = Result<Vec<Option<MyTerm>>, Infallible>>>; | ||
fn into_iter(self) -> Self::IntoIter { | ||
self.0 | ||
} | ||
} | ||
impl SparqlBindings<MyDataset> for MyBindings { | ||
fn variables(&self) -> Vec<&str> { | ||
vec!["s"] | ||
} | ||
} | ||
|
||
impl SparqlDataset for MyDataset { | ||
type BindingsTerm = MyTerm; | ||
type BindingsResult = MyBindings; | ||
type TriplesResult = Box<dyn Iterator<Item = Result<[MyTerm; 3], Infallible>>>; | ||
type SparqlError = Infallible; | ||
type Query = String; | ||
|
||
fn query<Q>(&self, query: Q) -> Result<SparqlResult<Self>, Self::SparqlError> | ||
where | ||
Q: ToQuery<String>, | ||
{ | ||
match query.to_query()?.borrow().as_ref() { | ||
"ASK" => Ok(SparqlResult::Boolean(true)), | ||
"GRAPH" => Ok(SparqlResult::Triples(Box::new( | ||
self.clone().into_iter().map(|q| Ok(q.0)), | ||
) | ||
as Self::TriplesResult)), | ||
_ => Ok(SparqlResult::Bindings(MyBindings(Box::new( | ||
self.subjects() | ||
.unwrap() | ||
.into_iter() | ||
.map(|t| Ok(vec![Some(t)])), | ||
)))), | ||
} | ||
} | ||
} | ||
} |