Skip to content

Commit

Permalink
API for SPARQL support (#91)
Browse files Browse the repository at this point in the history
This is the result of the discussions in #90 and #91.
Thanks to @Tpt and @yever for their valuable feedback.
  • Loading branch information
pchampin committed Oct 26, 2020
1 parent 2219d38 commit 16dadb1
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 0 deletions.
1 change: 1 addition & 0 deletions api/src/lib.rs
Expand Up @@ -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;
181 changes: 181 additions & 0 deletions api/src/sparql.rs
@@ -0,0 +1,181 @@
//! 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:
//!
//! - setting default values for `BASE`, `PREFIX`, `FROM`, `FROM NAMED` directives,
//! before parsing query string, or
//! - 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.
//!
//! # Extension point
//!
//! A possible way to extend these traits with additional functionalities
//! (such as the ones described above)
//! would be to define subtraits of `Query` with additional methods
//! (*e.g.*`set_base`, `bind_variables`...).
//!
//! Sophia may define such traits in the future,
//!
//! 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 separate parsing,
/// (or any other pre-processing step)
/// of the query string from the actual exectution of the query.
/// There is however no guarantee on how much pre-processing is actually done by this method
/// (see below).
///
/// # 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>;
}

0 comments on commit 16dadb1

Please sign in to comment.