Skip to main content

oxigraph/sparql/
mod.rs

1//! [SPARQL](https://www.w3.org/TR/sparql11-overview/) implementation.
2//!
3//! The entry point for SPARQL execution is the [`SparqlEvaluator`] type.
4
5mod algebra;
6mod dataset;
7mod error;
8#[cfg(feature = "http-client")]
9mod http;
10pub mod results;
11mod update;
12
13use crate::model::{NamedNode, Term};
14#[expect(deprecated)]
15pub use crate::sparql::algebra::{Query, Update};
16use crate::sparql::dataset::DatasetView;
17pub use crate::sparql::error::UpdateEvaluationError;
18#[cfg(feature = "http-client")]
19use crate::sparql::http::HttpServiceHandler;
20pub use crate::sparql::update::{BoundPreparedSparqlUpdate, PreparedSparqlUpdate};
21use crate::store::{Store, Transaction};
22use oxrdf::IriParseError;
23pub use oxrdf::{Variable, VariableNameParseError};
24pub use spareval::{
25    AggregateFunctionAccumulator, CancellationToken, DefaultServiceHandler,
26    QueryDatasetSpecification, QueryEvaluationError, QueryExplanation, QueryResults, QuerySolution,
27    QuerySolutionIter, QueryTripleIter, ServiceHandler,
28};
29use spareval::{QueryEvaluator, QueryableDataset};
30use spargebra::SparqlParser;
31pub use spargebra::SparqlSyntaxError;
32use std::collections::HashMap;
33use std::marker::PhantomData;
34use std::mem::take;
35#[cfg(feature = "http-client")]
36use std::time::Duration;
37
38#[deprecated(note = "Use SparqlEvaluator instead", since = "0.5.0")]
39pub type QueryOptions = SparqlEvaluator;
40#[deprecated(note = "Use SparqlEvaluator instead", since = "0.5.0")]
41pub type UpdateOptions = SparqlEvaluator;
42#[deprecated(note = "Use QueryEvaluationError instead", since = "0.5.0")]
43pub type EvaluationError = QueryEvaluationError;
44
45pub type QueryDataset = QueryDatasetSpecification;
46
47/// SPARQL evaluator.
48///
49/// It supports [SPARQL 1.1 query](https://www.w3.org/TR/sparql11-query/) and [SPARQL 1.1 update](https://www.w3.org/TR/sparql11-update/).
50///
51/// If the `"http-client"` optional feature is enabled,
52/// 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.
53///
54/// Usage example disabling the federated query support:
55/// ```
56/// use oxigraph::model::NamedNode;
57/// use oxigraph::sparql::SparqlEvaluator;
58/// use oxigraph::store::Store;
59///
60/// SparqlEvaluator::new()
61///     .with_custom_function(NamedNode::new("http://example.com/identity")?, |args| {
62///         args.get(0).cloned()
63///     })
64///     .parse_query("SELECT (<http://example.com/identity>('foo') AS ?r) WHERE {}")?
65///     .on_store(&Store::new()?)
66///     .execute()?;
67/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
68/// ```
69#[derive(Clone)]
70#[must_use]
71pub struct SparqlEvaluator {
72    #[cfg(feature = "http-client")]
73    http_timeout: Option<Duration>,
74    #[cfg(feature = "http-client")]
75    http_redirection_limit: usize,
76    #[cfg(feature = "http-client")]
77    with_http_default_service_handler: bool,
78    parser: SparqlParser,
79    inner: QueryEvaluator,
80}
81
82impl SparqlEvaluator {
83    pub fn new() -> Self {
84        Self::default()
85    }
86
87    /// Provides an IRI that could be used to resolve the operation relative IRIs.
88    ///
89    /// ```
90    /// use oxigraph::model::*;
91    /// use oxigraph::sparql::{QueryResults, SparqlEvaluator};
92    /// use oxigraph::store::Store;
93    ///
94    /// if let QueryResults::Solutions(mut solutions) = SparqlEvaluator::new()
95    ///     .with_base_iri("http://example.com/")?
96    ///     .parse_query("SELECT (<> AS ?r) WHERE {}")?
97    ///     .on_store(&Store::new()?)
98    ///     .execute()?
99    /// {
100    ///     assert_eq!(
101    ///         solutions.next().unwrap()?.get("r"),
102    ///         Some(&NamedNode::new("http://example.com/")?.into())
103    ///     );
104    /// }
105    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
106    /// ```
107    #[inline]
108    pub fn with_base_iri(mut self, base_iri: impl Into<String>) -> Result<Self, IriParseError> {
109        self.parser = self.parser.with_base_iri(base_iri)?;
110        Ok(self)
111    }
112
113    /// Set a default IRI prefix used during parsing.
114    ///
115    /// ```
116    /// use oxigraph::model::*;
117    /// use oxigraph::sparql::{QueryResults, SparqlEvaluator};
118    /// use oxigraph::store::Store;
119    ///
120    /// if let QueryResults::Solutions(mut solutions) = SparqlEvaluator::new()
121    ///     .with_prefix("ex", "http://example.com/")?
122    ///     .parse_query("SELECT (ex: AS ?r) WHERE {}")?
123    ///     .on_store(&Store::new()?)
124    ///     .execute()?
125    /// {
126    ///     assert_eq!(
127    ///         solutions.next().unwrap()?.get("r"),
128    ///         Some(&NamedNode::new("http://example.com/")?.into())
129    ///     );
130    /// }
131    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
132    /// ```
133    #[inline]
134    pub fn with_prefix(
135        mut self,
136        prefix_name: impl Into<String>,
137        prefix_iri: impl Into<String>,
138    ) -> Result<Self, IriParseError> {
139        self.parser = self.parser.with_prefix(prefix_name, prefix_iri)?;
140        Ok(self)
141    }
142
143    /// Use a given [`ServiceHandler`] to execute [SPARQL 1.1 Federated Query](https://www.w3.org/TR/sparql11-federated-query/) SERVICE calls.
144    ///
145    /// See [`ServiceHandler`] for an example.
146    #[inline]
147    pub fn with_service_handler(
148        mut self,
149        service_name: impl Into<NamedNode>,
150        handler: impl ServiceHandler + 'static,
151    ) -> Self {
152        self.inner = self.inner.with_service_handler(service_name, handler);
153        self
154    }
155
156    /// Use a given [`DefaultServiceHandler`] to execute [SPARQL 1.1 Federated Query](https://www.w3.org/TR/sparql11-federated-query/) SERVICE calls if no explicit service handler is defined for the service.
157    ///
158    /// This replaces the default service handler that does HTTP requests to remote endpoints.
159    ///
160    /// See [`DefaultServiceHandler`] for an example.
161    #[inline]
162    pub fn with_default_service_handler(
163        mut self,
164        handler: impl DefaultServiceHandler + 'static,
165    ) -> Self {
166        #[cfg(feature = "http-client")]
167        {
168            self.with_http_default_service_handler = false;
169        }
170        self.inner = self.inner.with_default_service_handler(handler);
171        self
172    }
173
174    /// Disables the default `SERVICE` call implementation that does HTTP requests to remote endpoints.
175    #[cfg(feature = "http-client")]
176    #[inline]
177    pub fn without_default_http_service_handler(mut self) -> Self {
178        self.with_http_default_service_handler = false;
179        self
180    }
181
182    /// Disables the `SERVICE` calls
183    #[cfg(feature = "http-client")]
184    #[inline]
185    #[deprecated(
186        note = "Use `without_default_http_service_handler` instead",
187        since = "0.5.0"
188    )]
189    pub fn without_service_handler(self) -> Self {
190        self.without_default_http_service_handler()
191    }
192
193    /// Sets a timeout for HTTP requests done during SPARQL evaluation.
194    #[cfg(feature = "http-client")]
195    #[inline]
196    pub fn with_http_timeout(mut self, timeout: Duration) -> Self {
197        self.http_timeout = Some(timeout);
198        self
199    }
200
201    /// Sets an upper bound to the number of HTTP redirections followed per HTTP request done during SPARQL evaluation.
202    ///
203    /// By default, this value is `0`.
204    #[cfg(feature = "http-client")]
205    #[inline]
206    pub fn with_http_redirection_limit(mut self, redirection_limit: usize) -> Self {
207        self.http_redirection_limit = redirection_limit;
208        self
209    }
210
211    /// Adds a custom SPARQL evaluation function.
212    ///
213    /// Example with a function serializing terms to N-Triples:
214    /// ```
215    /// use oxigraph::model::*;
216    /// use oxigraph::sparql::{QueryResults, SparqlEvaluator};
217    /// use oxigraph::store::Store;
218    ///
219    /// if let QueryResults::Solutions(mut solutions) = SparqlEvaluator::new()
220    ///     .with_custom_function(
221    ///         NamedNode::new("http://www.w3.org/ns/formats/N-Triples")?,
222    ///         |args| args.get(0).map(|t| Literal::from(t.to_string()).into()),
223    ///     )
224    ///     .parse_query("SELECT (<http://www.w3.org/ns/formats/N-Triples>(1) AS ?nt) WHERE {}")?
225    ///     .on_store(&Store::new()?)
226    ///     .execute()?
227    /// {
228    ///     assert_eq!(
229    ///         solutions.next().unwrap()?.get("nt"),
230    ///         Some(&Literal::from("\"1\"^^<http://www.w3.org/2001/XMLSchema#integer>").into())
231    ///     );
232    /// }
233    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
234    /// ```
235    #[inline]
236    pub fn with_custom_function(
237        mut self,
238        name: NamedNode,
239        evaluator: impl Fn(&[Term]) -> Option<Term> + Send + Sync + 'static,
240    ) -> Self {
241        self.inner = self.inner.with_custom_function(name, evaluator);
242        self
243    }
244
245    /// Adds a custom SPARQL evaluation aggregate function.
246    ///
247    /// Example with a function doing concatenation:
248    /// ```
249    /// use oxigraph::model::{Literal, NamedNode, Term};
250    /// use oxigraph::sparql::{AggregateFunctionAccumulator, QueryResults, SparqlEvaluator};
251    /// use oxigraph::store::Store;
252    /// use std::mem::take;
253    ///
254    /// struct ConcatAccumulator {
255    ///     value: String,
256    /// }
257    ///
258    /// impl AggregateFunctionAccumulator for ConcatAccumulator {
259    ///     fn accumulate(&mut self, element: Term) {
260    ///         if let Term::Literal(v) = element {
261    ///             if !self.value.is_empty() {
262    ///                 self.value.push(' ');
263    ///             }
264    ///             self.value.push_str(v.value());
265    ///         }
266    ///     }
267    ///
268    ///     fn finish(&mut self) -> Option<Term> {
269    ///         Some(Literal::new_simple_literal(take(&mut self.value)).into())
270    ///     }
271    /// }
272    ///
273    /// if let QueryResults::Solutions(mut solutions) = SparqlEvaluator::new()
274    ///     .with_custom_aggregate_function(NamedNode::new("http://example.com/concat")?, || {
275    ///         Box::new(ConcatAccumulator {
276    ///             value: String::new(),
277    ///         })
278    ///     })
279    ///     .parse_query(
280    ///         "SELECT (<http://example.com/concat>(?v) AS ?r) WHERE { VALUES ?v { 1 2 3 } }",
281    ///     )?
282    ///     .on_store(&Store::new()?)
283    ///     .execute()?
284    /// {
285    ///     assert_eq!(
286    ///         solutions.next().unwrap()?.get("r"),
287    ///         Some(&Literal::new_simple_literal("1 2 3").into())
288    ///     );
289    /// }
290    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
291    /// ```
292    #[inline]
293    pub fn with_custom_aggregate_function(
294        mut self,
295        name: NamedNode,
296        evaluator: impl Fn() -> Box<dyn AggregateFunctionAccumulator + Send + Sync>
297        + Send
298        + Sync
299        + 'static,
300    ) -> Self {
301        self.parser = self.parser.with_custom_aggregate_function(name.clone());
302        self.inner = self.inner.with_custom_aggregate_function(name, evaluator);
303        self
304    }
305
306    #[doc(hidden)]
307    #[inline]
308    pub fn without_optimizations(mut self) -> Self {
309        self.inner = self.inner.without_optimizations();
310        self
311    }
312
313    /// Inject a cancellation token to the SPARQL evaluation.
314    ///
315    /// Might be used to abort a query cleanly.
316    ///
317    /// ```
318    /// use oxigraph::model::*;
319    /// use oxigraph::sparql::{
320    ///     CancellationToken, QueryEvaluationError, QueryResults, SparqlEvaluator,
321    /// };
322    /// use oxigraph::store::Store;
323    ///
324    /// let store = Store::new()?;
325    /// store.insert(QuadRef::new(
326    ///     NamedNodeRef::new("http://example.com/s")?,
327    ///     NamedNodeRef::new("http://example.com/p")?,
328    ///     NamedNodeRef::new("http://example.com/o")?,
329    ///     GraphNameRef::DefaultGraph,
330    /// ))?;
331    /// let cancellation_token = CancellationToken::new();
332    /// if let QueryResults::Solutions(mut solutions) = SparqlEvaluator::new()
333    ///     .with_cancellation_token(cancellation_token.clone())
334    ///     .parse_query("SELECT * WHERE { ?s ?p ?o }")?
335    ///     .on_store(&store)
336    ///     .execute()?
337    /// {
338    ///     cancellation_token.cancel(); // We cancel
339    ///     assert!(matches!(
340    ///         solutions.next().unwrap().unwrap_err(),
341    ///         QueryEvaluationError::Cancelled
342    ///     ));
343    /// }
344    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
345    /// ```
346    pub fn with_cancellation_token(mut self, cancellation_token: CancellationToken) -> Self {
347        self.inner = self.inner.with_cancellation_token(cancellation_token);
348        self
349    }
350
351    #[cfg_attr(not(feature = "http-client"), expect(unused_mut))]
352    fn into_evaluator(mut self) -> QueryEvaluator {
353        #[cfg(feature = "http-client")]
354        if self.with_http_default_service_handler {
355            self.inner = self
356                .inner
357                .with_default_service_handler(HttpServiceHandler::new(
358                    self.http_timeout,
359                    self.http_redirection_limit,
360                ))
361        }
362        self.inner
363    }
364
365    /// Parse a query and returns a [`PreparedSparqlQuery`] for the current evaluator.
366    ///
367    /// Usage example:
368    /// ```
369    /// use oxigraph::model::*;
370    /// use oxigraph::sparql::{QueryResults, SparqlEvaluator};
371    /// use oxigraph::store::Store;
372    ///
373    /// let store = Store::new()?;
374    /// let ex = NamedNodeRef::new("http://example.com")?;
375    /// store.insert(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?;
376    ///
377    /// let prepared_query = SparqlEvaluator::new().parse_query("SELECT ?s WHERE { ?s ?p ?o }")?;
378    ///
379    /// if let QueryResults::Solutions(mut solutions) = prepared_query.on_store(&store).execute()? {
380    ///     assert_eq!(
381    ///         solutions.next().unwrap()?.get("s"),
382    ///         Some(&ex.into_owned().into())
383    ///     );
384    /// }
385    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
386    /// ```
387    pub fn parse_query(
388        mut self,
389        query: &(impl AsRef<str> + ?Sized),
390    ) -> Result<PreparedSparqlQuery, SparqlSyntaxError> {
391        let query = take(&mut self.parser).parse_query(query.as_ref())?;
392        Ok(self.for_query(query))
393    }
394
395    /// Returns a [`PreparedSparqlQuery`] for the current evaluator and SPARQL query.
396    ///
397    /// Usage example:
398    /// ```
399    /// use oxigraph::model::*;
400    /// use oxigraph::sparql::{QueryResults, SparqlEvaluator};
401    /// use oxigraph::store::Store;
402    /// use spargebra::SparqlParser;
403    ///
404    /// let store = Store::new()?;
405    /// let ex = NamedNodeRef::new("http://example.com")?;
406    /// store.insert(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?;
407    ///
408    /// let query = SparqlParser::new().parse_query("SELECT ?s WHERE { ?s ?p ?o }")?;
409    ///
410    /// let prepared_query = SparqlEvaluator::new().for_query(query);
411    ///
412    /// if let QueryResults::Solutions(mut solutions) = prepared_query.on_store(&store).execute()? {
413    ///     assert_eq!(
414    ///         solutions.next().unwrap()?.get("s"),
415    ///         Some(&ex.into_owned().into())
416    ///     );
417    /// }
418    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
419    /// ```
420    #[expect(deprecated)]
421    pub fn for_query(self, query: impl Into<Query>) -> PreparedSparqlQuery {
422        let query = query.into();
423        PreparedSparqlQuery {
424            dataset: query.dataset,
425            query: query.inner,
426            evaluator: self.into_evaluator(),
427            substitutions: HashMap::new(),
428        }
429    }
430
431    /// Parse an update and returns a [`PreparedSparqlUpdate`] for the current evaluator.
432    ///
433    /// Usage example:
434    /// ```
435    /// use oxigraph::sparql::SparqlEvaluator;
436    /// use oxigraph::store::Store;
437    ///
438    /// SparqlEvaluator::new()
439    ///     .parse_update(
440    ///         "INSERT DATA { <http://example.com> <http://example.com> <http://example.com> }",
441    ///     )?
442    ///     .on_store(&Store::new()?)
443    ///     .execute()?;
444    /// # Ok::<_, Box<dyn std::error::Error>>(())
445    /// ```
446    pub fn parse_update(
447        mut self,
448        query: &(impl AsRef<str> + ?Sized),
449    ) -> Result<PreparedSparqlUpdate, SparqlSyntaxError> {
450        let update = take(&mut self.parser).parse_update(query.as_ref())?;
451        Ok(self.for_update(update))
452    }
453
454    /// Returns a [`PreparedSparqlUpdate`] for the current evaluator and SPARQL update.
455    ///
456    /// Usage example:
457    /// ```
458    /// use oxigraph::sparql::SparqlEvaluator;
459    /// use oxigraph::store::Store;
460    /// use spargebra::SparqlParser;
461    ///
462    /// let update = SparqlParser::new().parse_update(
463    ///     "INSERT DATA { <http://example.com> <http://example.com> <http://example.com> }",
464    /// )?;
465    /// SparqlEvaluator::new()
466    ///     .for_update(update)
467    ///     .on_store(&Store::new()?)
468    ///     .execute()?;
469    /// # Ok::<_, Box<dyn std::error::Error>>(())
470    /// ```
471    #[expect(deprecated)]
472    pub fn for_update(self, update: impl Into<Update>) -> PreparedSparqlUpdate {
473        #[cfg(feature = "http-client")]
474        let http_timeout = self.http_timeout;
475        #[cfg(feature = "http-client")]
476        let http_redirection_limit = self.http_redirection_limit;
477        PreparedSparqlUpdate::new(
478            self.into_evaluator(),
479            update.into(),
480            #[cfg(feature = "http-client")]
481            http_timeout,
482            #[cfg(feature = "http-client")]
483            http_redirection_limit,
484        )
485    }
486}
487
488impl Default for SparqlEvaluator {
489    fn default() -> Self {
490        Self {
491            #[cfg(feature = "http-client")]
492            http_timeout: None,
493            #[cfg(feature = "http-client")]
494            http_redirection_limit: 0,
495            #[cfg(feature = "http-client")]
496            with_http_default_service_handler: true,
497            parser: SparqlParser::new(),
498            inner: QueryEvaluator::new(),
499        }
500    }
501}
502
503/// A prepared SPARQL query.
504///
505/// Allows customizing things like the evaluation dataset and substituting variables.
506///
507/// Usage example:
508/// ```
509/// use oxigraph::model::{Literal, Variable};
510/// use oxigraph::sparql::{QueryResults, SparqlEvaluator};
511/// use oxigraph::store::Store;
512///
513/// let prepared_query = SparqlEvaluator::new()
514///     .parse_query("SELECT ?v WHERE {}")?
515///     .substitute_variable(Variable::new("v")?, Literal::from(1));
516///
517/// if let QueryResults::Solutions(mut solutions) =
518///     prepared_query.on_store(&Store::new()?).execute()?
519/// {
520///     assert_eq!(
521///         solutions.next().unwrap()?.get("v"),
522///         Some(&Literal::from(1).into())
523///     );
524/// }
525/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
526/// ```
527#[derive(Clone)]
528#[must_use]
529pub struct PreparedSparqlQuery {
530    evaluator: QueryEvaluator,
531    query: spargebra::Query,
532    dataset: QueryDatasetSpecification,
533    substitutions: HashMap<Variable, Term>,
534}
535
536impl PreparedSparqlQuery {
537    /// Substitute a variable with a given RDF term in the SPARQL query.
538    ///
539    /// Usage example:
540    /// ```
541    /// use oxigraph::model::{Literal, Variable};
542    /// use oxigraph::sparql::{QueryResults, SparqlEvaluator};
543    /// use oxigraph::store::Store;
544    ///
545    /// let prepared_query = SparqlEvaluator::new()
546    ///     .parse_query("SELECT ?v WHERE {}")?
547    ///     .substitute_variable(Variable::new("v")?, Literal::from(1));
548    ///
549    /// if let QueryResults::Solutions(mut solutions) =
550    ///     prepared_query.on_store(&Store::new()?).execute()?
551    /// {
552    ///     assert_eq!(
553    ///         solutions.next().unwrap()?.get("v"),
554    ///         Some(&Literal::from(1).into())
555    ///     );
556    /// }
557    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
558    /// ```
559    #[inline]
560    pub fn substitute_variable(
561        mut self,
562        variable: impl Into<Variable>,
563        term: impl Into<Term>,
564    ) -> Self {
565        self.substitutions.insert(variable.into(), term.into());
566        self
567    }
568
569    /// Returns [the query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset) of this prepared query.
570    #[inline]
571    pub fn dataset(&self) -> &QueryDataset {
572        &self.dataset
573    }
574
575    /// Returns [the query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset) of this prepared query.
576    #[inline]
577    pub fn dataset_mut(&mut self) -> &mut QueryDataset {
578        &mut self.dataset
579    }
580
581    /// Bind the prepared query to the [`Store`] it should be evaluated on.
582    pub fn on_store(self, store: &Store) -> BoundPreparedSparqlQuery<'static> {
583        let reader = store.storage().snapshot();
584        let queryable_dataset = DatasetView::new(reader);
585        self.on_queryable_dataset(queryable_dataset)
586    }
587
588    /// Bind the prepared query to the [`Transaction`] it should be evaluated on.
589    pub fn on_transaction<'b>(
590        self,
591        transaction: &'b Transaction<'_>,
592    ) -> BoundPreparedSparqlQuery<'b> {
593        let reader = transaction.inner().reader();
594        let dataset = DatasetView::new(reader);
595        self.on_queryable_dataset(dataset)
596    }
597
598    /// Bind the prepared query to the [`QueryableDataset`] it should be evaluated on.
599    pub fn on_queryable_dataset<'a, D: QueryableDataset<'a>>(
600        self,
601        queryable_dataset: D,
602    ) -> BoundPreparedSparqlQuery<'a, D> {
603        BoundPreparedSparqlQuery {
604            evaluator: self.evaluator,
605            query: self.query,
606            queryable_dataset,
607            substitutions: self.substitutions,
608            dataset: self.dataset,
609            marker: PhantomData,
610        }
611    }
612}
613
614/// A prepared SPARQL query bound to a storage, ready to be executed.
615///
616/// Usage example:
617/// ```
618/// use oxigraph::model::{Literal, Variable};
619/// use oxigraph::sparql::{QueryResults, SparqlEvaluator};
620/// use oxigraph::store::Store;
621///
622/// let prepared_query = SparqlEvaluator::new()
623///     .parse_query("SELECT ?v WHERE {}")?
624///     .substitute_variable(Variable::new("v")?, Literal::from(1));
625///
626/// if let QueryResults::Solutions(mut solutions) =
627///     prepared_query.on_store(&Store::new()?).execute()?
628/// {
629///     assert_eq!(
630///         solutions.next().unwrap()?.get("v"),
631///         Some(&Literal::from(1).into())
632///     );
633/// }
634/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
635/// ```
636#[must_use]
637pub struct BoundPreparedSparqlQuery<'a, D: QueryableDataset<'a> = DatasetView<'a>> {
638    evaluator: QueryEvaluator,
639    query: spargebra::Query,
640    queryable_dataset: D,
641    substitutions: HashMap<Variable, Term>,
642    dataset: QueryDatasetSpecification,
643    marker: PhantomData<&'a ()>,
644}
645
646impl<'a, D: QueryableDataset<'a>> BoundPreparedSparqlQuery<'a, D> {
647    /// Substitute a variable with a given RDF term in the SPARQL query.
648    ///
649    /// Usage example:
650    /// ```
651    /// use oxigraph::model::{Literal, Variable};
652    /// use oxigraph::sparql::{QueryResults, SparqlEvaluator};
653    /// use oxigraph::store::Store;
654    ///
655    /// let prepared_query = SparqlEvaluator::new()
656    ///     .parse_query("SELECT ?v WHERE {}")?
657    ///     .on_store(&Store::new()?)
658    ///     .substitute_variable(Variable::new("v")?, Literal::from(1));
659    ///
660    /// if let QueryResults::Solutions(mut solutions) = prepared_query.execute()? {
661    ///     assert_eq!(
662    ///         solutions.next().unwrap()?.get("v"),
663    ///         Some(&Literal::from(1).into())
664    ///     );
665    /// }
666    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
667    /// ```
668    #[inline]
669    pub fn substitute_variable(
670        mut self,
671        variable: impl Into<Variable>,
672        term: impl Into<Term>,
673    ) -> Self {
674        self.substitutions.insert(variable.into(), term.into());
675        self
676    }
677
678    /// Evaluate the query against the given store.
679    pub fn execute(self) -> Result<QueryResults<'a>, QueryEvaluationError> {
680        let mut prepared = self.evaluator.prepare(&self.query);
681        for (variable, term) in self.substitutions {
682            prepared = prepared.substitute_variable(variable, term);
683        }
684        *prepared.dataset_mut() = self.dataset;
685        prepared.execute(self.queryable_dataset)
686    }
687
688    /// Compute statistics during evaluation and fills them in the explanation tree.
689    pub fn compute_statistics(mut self) -> Self {
690        self.evaluator = self.evaluator.compute_statistics();
691        self
692    }
693
694    /// Executes a [SPARQL 1.1 query](https://www.w3.org/TR/sparql11-query/) with some options and
695    /// returns a query explanation with some statistics (if enabled with the [`compute_statistics`](Self::compute_statistics) option).
696    ///
697    /// <div class="warning">If you want to compute statistics, you need to exhaust the results iterator before having a look at them.</div>
698    ///
699    /// Usage example serializing the explanation with statistics in JSON:
700    /// ```
701    /// use oxigraph::sparql::{QueryResults, SparqlEvaluator};
702    /// use oxigraph::store::Store;
703    ///
704    /// if let (Ok(QueryResults::Solutions(solutions)), explanation) = SparqlEvaluator::new()
705    ///     .parse_query("SELECT ?s WHERE { VALUES ?s { 1 2 3 } }")?
706    ///     .on_store(&Store::new()?)
707    ///     .explain()
708    /// {
709    ///     // We make sure to have read all the solutions
710    ///     for _ in solutions {}
711    ///     let mut buf = Vec::new();
712    ///     explanation.write_in_json(&mut buf)?;
713    /// }
714    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
715    /// ```
716    pub fn explain(
717        self,
718    ) -> (
719        Result<QueryResults<'a>, QueryEvaluationError>,
720        QueryExplanation,
721    ) {
722        let mut prepared = self.evaluator.prepare(&self.query);
723        for (variable, term) in self.substitutions {
724            prepared = prepared.substitute_variable(variable, term);
725        }
726        *prepared.dataset_mut() = self.dataset;
727        prepared.explain(self.queryable_dataset)
728    }
729}