oxigraph/sparql/
mod.rs

1//! [SPARQL](https://www.w3.org/TR/sparql11-overview/) implementation.
2//!
3//! Stores execute SPARQL. See [`Store`](crate::store::Store::query()) for an example.
4
5mod algebra;
6mod dataset;
7mod error;
8mod http;
9mod model;
10pub mod results;
11mod service;
12mod update;
13
14use crate::model::{NamedNode, Term};
15pub use crate::sparql::algebra::{Query, QueryDataset, Update};
16use crate::sparql::dataset::DatasetView;
17pub use crate::sparql::error::EvaluationError;
18pub use crate::sparql::model::{QueryResults, QuerySolution, QuerySolutionIter, QueryTripleIter};
19pub use crate::sparql::service::ServiceHandler;
20use crate::sparql::service::{EmptyServiceHandler, WrappedDefaultServiceHandler};
21pub(crate) use crate::sparql::update::evaluate_update;
22use crate::storage::StorageReader;
23pub use oxrdf::{Variable, VariableNameParseError};
24use spareval::QueryEvaluator;
25pub use spareval::QueryExplanation;
26pub use spargebra::SparqlSyntaxError;
27use std::time::Duration;
28
29#[allow(clippy::needless_pass_by_value)]
30pub(crate) fn evaluate_query(
31    reader: StorageReader,
32    query: impl TryInto<Query, Error = impl Into<EvaluationError>>,
33    options: QueryOptions,
34    run_stats: bool,
35    substitutions: impl IntoIterator<Item = (Variable, Term)>,
36) -> Result<(Result<QueryResults, EvaluationError>, QueryExplanation), EvaluationError> {
37    let query = query.try_into().map_err(Into::into)?;
38    let dataset = DatasetView::new(reader, &query.dataset);
39    let mut evaluator = options.into_evaluator();
40    if run_stats {
41        evaluator = evaluator.compute_statistics();
42    }
43    let (results, explanation) =
44        evaluator.explain_with_substituted_variables(dataset, &query.inner, substitutions);
45    let results = results.map_err(Into::into).map(Into::into);
46    Ok((results, explanation))
47}
48
49/// Options for SPARQL query evaluation.
50///
51///
52/// If the `"http-client"` optional feature is enabled,
53/// a simple HTTP 1.1 client is used to execute [SPARQL 1.1 Federated Query](https://www.w3.org/TR/sparql11-federated-query/) SERVICE calls.
54///
55/// Usage example disabling the federated query support:
56/// ```
57/// use oxigraph::sparql::QueryOptions;
58/// use oxigraph::store::Store;
59///
60/// let store = Store::new()?;
61/// store.query_opt(
62///     "SELECT * WHERE { SERVICE <https://query.wikidata.org/sparql> {} }",
63///     QueryOptions::default().without_service_handler(),
64/// )?;
65/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
66/// ```
67#[derive(Clone)]
68pub struct QueryOptions {
69    http_timeout: Option<Duration>,
70    http_redirection_limit: usize,
71    inner: QueryEvaluator,
72}
73
74impl QueryOptions {
75    /// Use a given [`ServiceHandler`] to execute [SPARQL 1.1 Federated Query](https://www.w3.org/TR/sparql11-federated-query/) SERVICE calls.
76    #[inline]
77    #[must_use]
78    pub fn with_service_handler(mut self, service_handler: impl ServiceHandler + 'static) -> Self {
79        self.inner = self
80            .inner
81            .with_default_service_handler(WrappedDefaultServiceHandler(service_handler));
82        self
83    }
84
85    /// Disables the `SERVICE` calls
86    #[inline]
87    #[must_use]
88    pub fn without_service_handler(mut self) -> Self {
89        self.inner = self.inner.with_default_service_handler(EmptyServiceHandler);
90        self
91    }
92
93    /// Sets a timeout for HTTP requests done during SPARQL evaluation.
94    #[cfg(feature = "http-client")]
95    #[inline]
96    #[must_use]
97    pub fn with_http_timeout(mut self, timeout: Duration) -> Self {
98        self.http_timeout = Some(timeout);
99        self
100    }
101
102    /// Sets an upper bound of the number of HTTP redirection followed per HTTP request done during SPARQL evaluation.
103    ///
104    /// By default this value is `0`.
105    #[cfg(feature = "http-client")]
106    #[inline]
107    #[must_use]
108    pub fn with_http_redirection_limit(mut self, redirection_limit: usize) -> Self {
109        self.http_redirection_limit = redirection_limit;
110        self
111    }
112
113    fn into_evaluator(mut self) -> QueryEvaluator {
114        if !self.inner.has_default_service_handler() {
115            self.inner =
116                self.inner
117                    .with_default_service_handler(service::SimpleServiceHandler::new(
118                        self.http_timeout,
119                        self.http_redirection_limit,
120                    ))
121        }
122        self.inner
123    }
124
125    /// Adds a custom SPARQL evaluation function.
126    ///
127    /// Example with a function serializing terms to N-Triples:
128    /// ```
129    /// use oxigraph::model::*;
130    /// use oxigraph::sparql::{QueryOptions, QueryResults};
131    /// use oxigraph::store::Store;
132    ///
133    /// let store = Store::new()?;
134    ///
135    /// if let QueryResults::Solutions(mut solutions) = store.query_opt(
136    ///     "SELECT (<http://www.w3.org/ns/formats/N-Triples>(1) AS ?nt) WHERE {}",
137    ///     QueryOptions::default().with_custom_function(
138    ///         NamedNode::new("http://www.w3.org/ns/formats/N-Triples")?,
139    ///         |args| args.get(0).map(|t| Literal::from(t.to_string()).into()),
140    ///     ),
141    /// )? {
142    ///     assert_eq!(
143    ///         solutions.next().unwrap()?.get("nt"),
144    ///         Some(&Literal::from("\"1\"^^<http://www.w3.org/2001/XMLSchema#integer>").into())
145    ///     );
146    /// }
147    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
148    /// ```
149    #[inline]
150    #[must_use]
151    pub fn with_custom_function(
152        mut self,
153        name: NamedNode,
154        evaluator: impl Fn(&[Term]) -> Option<Term> + Send + Sync + 'static,
155    ) -> Self {
156        self.inner = self.inner.with_custom_function(name, evaluator);
157        self
158    }
159
160    #[doc(hidden)]
161    #[inline]
162    #[must_use]
163    pub fn without_optimizations(mut self) -> Self {
164        self.inner = self.inner.without_optimizations();
165        self
166    }
167}
168
169impl Default for QueryOptions {
170    fn default() -> Self {
171        let mut options = Self {
172            http_timeout: None,
173            http_redirection_limit: 0,
174            inner: QueryEvaluator::new(),
175        };
176        if cfg!(feature = "http-client") {
177            options.inner =
178                options
179                    .inner
180                    .with_default_service_handler(service::SimpleServiceHandler::new(
181                        options.http_timeout,
182                        options.http_redirection_limit,
183                    ));
184        }
185        options
186    }
187}
188
189/// Options for SPARQL update evaluation.
190#[derive(Clone, Default)]
191pub struct UpdateOptions {
192    query_options: QueryOptions,
193}
194
195impl From<QueryOptions> for UpdateOptions {
196    #[inline]
197    fn from(query_options: QueryOptions) -> Self {
198        Self { query_options }
199    }
200}