diff --git a/api/src/lib.rs b/api/src/lib.rs index 4ff9f1e..42463f8 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -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; diff --git a/api/src/sparql.rs b/api/src/sparql.rs new file mode 100644 index 0000000..406a53a --- /dev/null +++ b/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; + /// 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; + + /// 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(&self, query: Q) -> Result, Self::SparqlError> + where + Q: ToQuery; + + /// 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::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; +} + +impl Query for String { + type Error = std::convert::Infallible; + fn parse(query_source: &str) -> Result { + 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 { + type Out: Borrow; + fn to_query(self) -> Result; +} + +impl<'a, Q> ToQuery for &'a Q +where + Q: Query, +{ + type Out = &'a Q; + fn to_query(self) -> Result { + Ok(self) + } +} + +impl<'a, Q> ToQuery for &'a str +where + Q: Query, +{ + type Out = Q; + fn to_query(self) -> Result { + Q::parse(self) + } +} + +/// The result of executing a SPARQL query. +pub enum SparqlResult +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 SparqlResult +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: + IntoIterator>, D::SparqlError>> +where + D: SparqlDataset + ?Sized, +{ + /// Return the list of SELECTed variable names + fn variables(&self) -> Vec<&str>; +}