sophia_api/
sparql.rs

1//! Common traits for working with [SPARQL](https://www.w3.org/TR/sparql11-query/).
2//!
3//! # Design rationale
4//!
5//! These traits are deliberately very generic.
6//! Specific implementations may have additional features, such as:
7//!
8//! - setting default values for `BASE`, `PREFIX`, `FROM`, `FROM NAMED` directives,
9//!   before parsing query string, or
10//! - pre-binding variables before evaluating query;
11//! - etc...
12//!
13//! However, we do not want to impose these feature, or any subset thereof,
14//! to all implementations of Sophia.
15//!
16//! # Extension point
17//!
18//! A possible way to extend these traits with additional functionalities
19//! (such as the ones described above)
20//! would be to define subtraits of `Query` with additional methods
21//! (*e.g.*`set_base`, `bind_variables`...).
22//! Implementation could then express requirements as trait bound, e.g.:
23//! ```ignore
24//!     D: SparqlDataset,
25//!     D::Query: Clone + BindVariable,
26//! ```
27//!
28//! Sophia may define such traits in the future.
29
30use crate::source::TripleSource;
31use crate::term::Term;
32use std::borrow::Borrow;
33use std::error::Error;
34
35/// A dataset that can be queried with SPARQL.
36pub trait SparqlDataset {
37    /// The type of terms that SELECT queries will return.
38    type BindingsTerm: Term;
39    /// The type of bindings that SELECT queries will return.
40    type BindingsResult: SparqlBindings<Self>;
41    /// The type of triples that GRAPH and DESCRIBE queries will return.
42    type TriplesResult: TripleSource;
43    /// The type of errors that processing SPARQL queries may raise.
44    type SparqlError: Error + 'static;
45    /// The type representing pre-processed queries.
46    ///
47    /// See [`prepare_query`](#tymethod.prepare_query) for more defail.
48    type Query: Query<Error = Self::SparqlError>;
49
50    /// Parse and immediately execute `query`.
51    ///
52    /// `query` is usually either a `&str` that will be parsed on the fly,
53    /// or a `Self::Query` that was earlier prepared by the [`prepare_query`] method.
54    ///
55    /// [`prepare_query`]: #method.prepared
56    fn query<Q>(&self, query: Q) -> Result<SparqlResult<Self>, Self::SparqlError>
57    where
58        Q: IntoQuery<Self::Query>;
59
60    /// Prepare a query for multiple future executions.
61    ///
62    /// This allows some implementation to separate parsing,
63    /// (or any other pre-processing step)
64    /// of the query string from the actual exectution of the query.
65    /// There is however no guarantee on how much pre-processing is actually done by this method
66    /// (see below).
67    ///
68    /// # Note to implementers
69    ///
70    /// If it is impossible or inconvenient to provide a type for pre-parsed queries,
71    /// you can still use `String`, which implements the [`Query`] trait.
72    fn prepare_query(&self, query_string: &str) -> Result<Self::Query, Self::SparqlError> {
73        Self::Query::parse(query_string)
74    }
75}
76
77/// Preprocessed query, ready for execution.
78///
79/// This trait exist to allow *some* implementations of [`SparqlDataset`]
80/// to mutualize the parsing of queries in the
81/// [`prepare_query`](SparqlDataset::prepare_query) method.
82pub trait Query: Sized {
83    /// The error type that might be raised when parsing a query.
84    type Error: Error + 'static;
85    /// Parse the given text into a [`Query`].
86    fn parse(query_source: &str) -> Result<Self, Self::Error>;
87}
88
89impl Query for String {
90    type Error = std::convert::Infallible;
91    fn parse(query_source: &str) -> Result<Self, Self::Error> {
92        Ok(query_source.into())
93    }
94}
95
96/// A utility trait to allow [`SparqlDataset::query`]
97/// to accept either `&str` or [`Self::Query`](SparqlDataset::Query).
98pub trait IntoQuery<Q: Query> {
99    /// The ouput type of [`into_query`](IntoQuery::into_query).
100    type Out: Borrow<Q>;
101    /// Convert `self` to a [`Query`].
102    fn into_query(self) -> Result<Self::Out, Q::Error>;
103}
104
105impl<'a, Q> IntoQuery<Q> for &'a Q
106where
107    Q: Query,
108{
109    type Out = &'a Q;
110    fn into_query(self) -> Result<Self::Out, Q::Error> {
111        Ok(self)
112    }
113}
114
115impl<'a, Q> IntoQuery<Q> for &'a str
116where
117    Q: Query,
118{
119    type Out = Q;
120    fn into_query(self) -> Result<Self::Out, Q::Error> {
121        Q::parse(self)
122    }
123}
124
125/// The result of executing a SPARQL query.
126pub enum SparqlResult<T>
127where
128    T: SparqlDataset + ?Sized,
129{
130    /// The result of a SELECT query
131    Bindings(T::BindingsResult),
132    /// The result of an ASK query
133    Boolean(bool),
134    /// The result of a CONSTRUCT or DESCRIBE query
135    Triples(T::TriplesResult),
136}
137
138impl<T> SparqlResult<T>
139where
140    T: SparqlDataset + ?Sized,
141{
142    /// Get this result as a `Bindings`.
143    ///
144    /// # Panics
145    /// This will panic if `self` is actually of another kind.
146    pub fn into_bindings(self) -> T::BindingsResult {
147        match self {
148            SparqlResult::Bindings(b) => b,
149            _ => panic!("This SparqlResult is not a Bindings"),
150        }
151    }
152    /// Get this result as a `Boolean`.
153    ///
154    /// # Panics
155    /// This will panic if `self` is actually of another kind.
156    pub fn into_boolean(self) -> bool {
157        match self {
158            SparqlResult::Boolean(b) => b,
159            _ => panic!("This SparqlResult is not a Boolean"),
160        }
161    }
162    /// Get this result as a `Triples`.
163    ///
164    /// # Panics
165    /// This will panic if `self` is actually of another kind.
166    pub fn into_triples(self) -> T::TriplesResult {
167        match self {
168            SparqlResult::Triples(t) => t,
169            _ => panic!("This SparqlResult is not a Triples"),
170        }
171    }
172}
173
174/// The result of executing a SPARQL SELECT query
175pub trait SparqlBindings<D>:
176    IntoIterator<Item = Result<Vec<Option<D::BindingsTerm>>, D::SparqlError>>
177where
178    D: SparqlDataset + ?Sized,
179{
180    /// Return the list of SELECTed variable names
181    fn variables(&self) -> Vec<&str>;
182}