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//! ```text
24//!     D: SparqlDataset,
25//!     D::Query: Clone + BindVariable,
26//! ```
27//!
28//! Sophia may define such traits in the future.
29
30use sophia_iri::Iri;
31
32use crate::source::TripleSource;
33use crate::term::Term;
34
35use std::borrow::Borrow;
36use std::error::Error;
37
38/// A dataset that can be queried with SPARQL.
39pub trait SparqlDataset {
40    /// The type of terms that SELECT queries will return.
41    type BindingsTerm: Term;
42    /// The type of bindings that SELECT queries will return.
43    type BindingsResult: SparqlBindings<Self>;
44    /// The type of triples that GRAPH and DESCRIBE queries will return.
45    type TriplesResult: TripleSource;
46    /// The type of errors that processing SPARQL queries may raise.
47    type SparqlError: Error + Send + Sync + 'static;
48    /// The type representing pre-processed queries.
49    ///
50    /// See [`prepare_query`](#tymethod.prepare_query) for more detail.
51    type Query: Query<Error = Self::SparqlError>;
52
53    /// Parse and immediately execute `query`.
54    ///
55    /// `query` is usually either a `&str` that will be parsed on the fly,
56    /// or a `Self::Query` that was earlier prepared by the [`prepare_query`] method.
57    ///
58    /// [`prepare_query`]: #method.prepared
59    fn query<Q>(&self, query: Q) -> Result<SparqlResult<Self>, Self::SparqlError>
60    where
61        Q: IntoQuery<Self::Query>;
62
63    /// Prepare a query for multiple future executions.
64    ///
65    /// This allows some implementation to separate parsing,
66    /// (or any other pre-processing step)
67    /// of the query string from the actual execution of the query.
68    /// There is however no guarantee on how much pre-processing is actually done by this method
69    /// (see below).
70    ///
71    /// # Note to implementers
72    ///
73    /// If it is impossible or inconvenient to provide a type for pre-parsed queries,
74    /// you can still use `String`, which implements the [`Query`] trait.
75    ///
76    /// See also [`SparqlDataset::prepare_query_with`]
77    fn prepare_query(&self, query_string: &str) -> Result<Self::Query, Self::SparqlError> {
78        Self::Query::parse(query_string)
79    }
80
81    /// Prepare a query for multiple future executions,
82    /// using the given IRI to resolve relative IRIs.
83    ///
84    /// See also [`SparqlDataset::prepare_query`].
85    fn prepare_query_with(
86        &self,
87        query_string: &str,
88        base: Iri<&str>,
89    ) -> Result<Self::Query, Self::SparqlError> {
90        Self::Query::parse_with(query_string, base)
91    }
92}
93
94/// Preprocessed query, ready for execution.
95///
96/// This trait exist to allow *some* implementations of [`SparqlDataset`]
97/// to mutualize the parsing of queries in the
98/// [`prepare_query`](SparqlDataset::prepare_query) method.
99pub trait Query: Sized {
100    /// The error type that might be raised when parsing a query.
101    type Error: Error + Send + Sync + 'static;
102    /// Parse the given text into a [`Query`].
103    fn parse(query_source: &str) -> Result<Self, Self::Error>;
104    /// Parse the given text into a [`Query`], using the given base IRI
105    fn parse_with(query_source: &str, base: Iri<&str>) -> Result<Self, Self::Error>;
106}
107
108impl Query for String {
109    type Error = std::convert::Infallible;
110    fn parse(query_source: &str) -> Result<Self, Self::Error> {
111        Ok(query_source.into())
112    }
113    fn parse_with(query_source: &str, base: Iri<&str>) -> Result<Self, Self::Error> {
114        Ok(format!("BASE <{base}>\n{query_source}"))
115    }
116}
117
118/// A utility trait to allow [`SparqlDataset::query`]
119/// to accept either `&str` or [`Self::Query`](SparqlDataset::Query).
120pub trait IntoQuery<Q: Query> {
121    /// The output type of [`into_query`](IntoQuery::into_query).
122    type Out: Borrow<Q>;
123    /// Convert `self` to a [`Query`].
124    fn into_query(self) -> Result<Self::Out, Q::Error>;
125}
126
127impl<'a, Q> IntoQuery<Q> for &'a Q
128where
129    Q: Query,
130{
131    type Out = &'a Q;
132    fn into_query(self) -> Result<Self::Out, Q::Error> {
133        Ok(self)
134    }
135}
136
137impl<'a, Q> IntoQuery<Q> for &'a str
138where
139    Q: Query,
140{
141    type Out = Q;
142    fn into_query(self) -> Result<Self::Out, Q::Error> {
143        Q::parse(self)
144    }
145}
146
147/// The result of executing a SPARQL query.
148pub enum SparqlResult<T>
149where
150    T: SparqlDataset + ?Sized,
151{
152    /// The result of a SELECT query
153    Bindings(T::BindingsResult),
154    /// The result of an ASK query
155    Boolean(bool),
156    /// The result of a CONSTRUCT or DESCRIBE query
157    Triples(T::TriplesResult),
158}
159
160impl<T> SparqlResult<T>
161where
162    T: SparqlDataset + ?Sized,
163{
164    /// Get this result as a `Bindings`.
165    ///
166    /// # Panics
167    /// This will panic if `self` is actually of another kind.
168    pub fn into_bindings(self) -> T::BindingsResult {
169        match self {
170            SparqlResult::Bindings(b) => b,
171            _ => panic!("This SparqlResult is not a Bindings"),
172        }
173    }
174    /// Get this result as a `Boolean`.
175    ///
176    /// # Panics
177    /// This will panic if `self` is actually of another kind.
178    pub fn into_boolean(self) -> bool {
179        match self {
180            SparqlResult::Boolean(b) => b,
181            _ => panic!("This SparqlResult is not a Boolean"),
182        }
183    }
184    /// Get this result as a `Triples`.
185    ///
186    /// # Panics
187    /// This will panic if `self` is actually of another kind.
188    pub fn into_triples(self) -> T::TriplesResult {
189        match self {
190            SparqlResult::Triples(t) => t,
191            _ => panic!("This SparqlResult is not a Triples"),
192        }
193    }
194}
195
196/// The result of executing a SPARQL SELECT query
197pub trait SparqlBindings<D>:
198    IntoIterator<Item = Result<Vec<Option<D::BindingsTerm>>, D::SparqlError>>
199where
200    D: SparqlDataset + ?Sized,
201{
202    /// Return the list of SELECTed variable names
203    fn variables(&self) -> Vec<&str>;
204}