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}