spareval/lib.rs
1#![doc = include_str!("../README.md")]
2#![doc(test(attr(deny(warnings))))]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4#![doc(html_favicon_url = "https://raw.githubusercontent.com/oxigraph/oxigraph/main/logo.svg")]
5#![doc(html_logo_url = "https://raw.githubusercontent.com/oxigraph/oxigraph/main/logo.svg")]
6
7mod dataset;
8mod error;
9mod eval;
10mod expression;
11mod model;
12mod service;
13mod update;
14
15#[cfg(feature = "sparql-12")]
16pub use crate::dataset::ExpressionTriple;
17pub use crate::dataset::{ExpressionTerm, InternalQuad, QueryableDataset};
18pub use crate::error::QueryEvaluationError;
19pub use crate::eval::CancellationToken;
20use crate::eval::{EvalNodeWithStats, SimpleEvaluator, Timer};
21use crate::expression::{
22 CustomFunctionRegistry, ExpressionEvaluatorContext, build_expression_evaluator,
23};
24pub use crate::model::{QueryResults, QuerySolution, QuerySolutionIter, QueryTripleIter};
25use crate::service::ServiceHandlerRegistry;
26pub use crate::service::{DefaultServiceHandler, ServiceHandler};
27pub use crate::update::{DeleteInsertIter, DeleteInsertQuad};
28use json_event_parser::{JsonEvent, WriterJsonSerializer};
29use oxiri::Iri;
30use oxrdf::{GraphName, Literal, NamedNode, NamedOrBlankNode, Term, Variable};
31use oxsdatatypes::{DateTime, DayTimeDuration, Float};
32use spargebra::Query;
33use spargebra::algebra::QueryDataset;
34use spargebra::term::{GroundQuadPattern, QuadPattern};
35use sparopt::Optimizer;
36use sparopt::algebra::GraphPattern;
37use std::collections::HashMap;
38use std::rc::Rc;
39use std::sync::Arc;
40use std::{fmt, io};
41
42/// Evaluates a query against a given [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset)
43///
44/// To adapt this software to work on your own RDF dataset, you need to implement the [`QueryableDataset`] trait.
45///
46/// ```
47/// use oxrdf::{Dataset, GraphName, NamedNode, Quad};
48/// use spareval::{QueryEvaluator, QueryResults};
49/// use spargebra::SparqlParser;
50///
51/// let ex = NamedNode::new("http://example.com")?;
52/// let dataset = Dataset::from_iter([Quad::new(
53/// ex.clone(),
54/// ex.clone(),
55/// ex.clone(),
56/// GraphName::DefaultGraph,
57/// )]);
58/// let query = SparqlParser::new().parse_query("SELECT * WHERE { ?s ?p ?o }")?;
59/// let evaluator = QueryEvaluator::new();
60/// let results = evaluator.prepare(&query).execute(&dataset)?;
61/// if let QueryResults::Solutions(solutions) = results {
62/// let solutions = solutions.collect::<Result<Vec<_>, _>>()?;
63/// assert_eq!(solutions.len(), 1);
64/// assert_eq!(solutions[0]["s"], ex.into());
65/// }
66/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
67/// ```
68#[derive(Clone, Default)]
69pub struct QueryEvaluator {
70 service_handler: ServiceHandlerRegistry,
71 custom_functions: CustomFunctionRegistry,
72 custom_aggregate_functions: CustomAggregateFunctionRegistry,
73 without_optimizations: bool,
74 run_stats: bool,
75 cancellation_token: Option<CancellationToken>,
76}
77
78impl QueryEvaluator {
79 #[must_use]
80 #[inline]
81 pub fn new() -> Self {
82 Self::default()
83 }
84
85 /// Prepare the SPARQL query to be executed.
86 pub fn prepare<'a>(&'a self, query: &'a Query) -> PreparedQuery<'a> {
87 let dataset = query.dataset().cloned().map(Into::into).unwrap_or_default();
88 PreparedQuery {
89 evaluator: self,
90 query,
91 dataset,
92 substitutions: HashMap::new(),
93 }
94 }
95
96 /// Execute the SPARQL query against the given dataset.
97 ///
98 /// Note that this evaluator does not handle the `FROM` and `FROM NAMED` part of the query.
99 /// You must select the proper dataset before using this struct.
100 #[deprecated(since = "0.2.1", note = "Use prepare instead")]
101 #[expect(deprecated)]
102 pub fn execute<'a>(
103 &self,
104 dataset: impl QueryableDataset<'a>,
105 query: &Query,
106 ) -> Result<QueryResults<'a>, QueryEvaluationError> {
107 self.execute_with_substituted_variables(dataset, query, [])
108 }
109
110 /// Executes a SPARQL query while substituting some variables with the given values.
111 ///
112 /// Substitution follows [RDF-dev SEP-0007](https://github.com/w3c/sparql-dev/blob/main/SEP/SEP-0007/sep-0007.md).
113 ///
114 /// ```
115 /// # #![expect(deprecated)]
116 /// use oxrdf::{Dataset, GraphName, NamedNode, Quad, Variable};
117 /// use spareval::{QueryEvaluator, QueryResults};
118 /// use spargebra::SparqlParser;
119 ///
120 /// let ex = NamedNode::new("http://example.com")?;
121 /// let dataset = Dataset::from_iter([Quad::new(
122 /// ex.clone(),
123 /// ex.clone(),
124 /// ex.clone(),
125 /// GraphName::DefaultGraph,
126 /// )]);
127 /// let query = SparqlParser::new().parse_query("SELECT * WHERE { ?s ?p ?o }")?;
128 /// let results = QueryEvaluator::new().execute_with_substituted_variables(
129 /// &dataset,
130 /// &query,
131 /// [(Variable::new("s")?, ex.clone().into())],
132 /// );
133 /// if let QueryResults::Solutions(solutions) = results? {
134 /// let solutions = solutions.collect::<Result<Vec<_>, _>>()?;
135 /// assert_eq!(solutions.len(), 1);
136 /// assert_eq!(solutions[0]["s"], ex.into());
137 /// }
138 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
139 /// ```
140 #[deprecated(since = "0.2.1", note = "Use prepare instead")]
141 #[expect(deprecated)]
142 pub fn execute_with_substituted_variables<'a>(
143 &self,
144 dataset: impl QueryableDataset<'a>,
145 query: &Query,
146 substitutions: impl IntoIterator<Item = (Variable, Term)>,
147 ) -> Result<QueryResults<'a>, QueryEvaluationError> {
148 self.explain_with_substituted_variables(dataset, query, substitutions)
149 .0
150 }
151
152 #[deprecated(since = "0.2.1", note = "Use prepare instead")]
153 #[expect(deprecated)]
154 pub fn explain<'a>(
155 &self,
156 dataset: impl QueryableDataset<'a>,
157 query: &Query,
158 ) -> (
159 Result<QueryResults<'a>, QueryEvaluationError>,
160 QueryExplanation,
161 ) {
162 self.explain_with_substituted_variables(dataset, query, [])
163 }
164
165 #[deprecated(since = "0.2.1", note = "Use prepare instead")]
166 pub fn explain_with_substituted_variables<'a>(
167 &self,
168 dataset: impl QueryableDataset<'a>,
169 query: &Query,
170 substitutions: impl IntoIterator<Item = (Variable, Term)>,
171 ) -> (
172 Result<QueryResults<'a>, QueryEvaluationError>,
173 QueryExplanation,
174 ) {
175 let mut prepared = PreparedQuery {
176 evaluator: self,
177 query,
178 dataset: QueryDatasetSpecification::new(),
179 substitutions: HashMap::new(),
180 };
181 for (variable, term) in substitutions {
182 prepared = prepared.substitute_variable(variable, term);
183 }
184 prepared.explain(dataset)
185 }
186
187 /// Use a given [`ServiceHandler`] to execute [SPARQL 1.1 Federated Query](https://www.w3.org/TR/sparql11-federated-query/) SERVICE calls.
188 ///
189 /// See [`ServiceHandler`] for an example.
190 #[inline]
191 #[must_use]
192 pub fn with_service_handler(
193 mut self,
194 service_name: impl Into<NamedNode>,
195 handler: impl ServiceHandler + 'static,
196 ) -> Self {
197 self.service_handler = self
198 .service_handler
199 .with_handler(service_name.into(), handler);
200 self
201 }
202
203 /// 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.
204 ///
205 /// See [`DefaultServiceHandler`] for an example.
206 #[inline]
207 #[must_use]
208 pub fn with_default_service_handler(
209 mut self,
210 handler: impl DefaultServiceHandler + 'static,
211 ) -> Self {
212 self.service_handler = self.service_handler.with_default_handler(handler);
213 self
214 }
215
216 #[inline]
217 #[must_use]
218 pub fn has_default_service_handler(&self) -> bool {
219 self.service_handler.has_default_handler()
220 }
221
222 /// Adds a custom SPARQL evaluation function.
223 ///
224 /// Example with a function serializing terms to N-Triples:
225 /// ```
226 /// use oxrdf::{Dataset, Literal, NamedNode};
227 /// use spareval::{QueryEvaluator, QueryResults};
228 /// use spargebra::SparqlParser;
229 ///
230 /// let evaluator = QueryEvaluator::new().with_custom_function(
231 /// NamedNode::new("http://www.w3.org/ns/formats/N-Triples")?,
232 /// |args| args.get(0).map(|t| Literal::from(t.to_string()).into()),
233 /// );
234 /// let query = SparqlParser::new()
235 /// .parse_query("SELECT (<http://www.w3.org/ns/formats/N-Triples>(1) AS ?nt) WHERE {}")?;
236 /// if let QueryResults::Solutions(mut solutions) =
237 /// evaluator.prepare(&query).execute(&Dataset::new())?
238 /// {
239 /// assert_eq!(
240 /// solutions.next().unwrap()?.get("nt"),
241 /// Some(&Literal::from("\"1\"^^<http://www.w3.org/2001/XMLSchema#integer>").into())
242 /// );
243 /// }
244 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
245 /// ```
246 #[inline]
247 #[must_use]
248 pub fn with_custom_function(
249 mut self,
250 name: NamedNode,
251 evaluator: impl Fn(&[Term]) -> Option<Term> + Send + Sync + 'static,
252 ) -> Self {
253 self.custom_functions.insert(name, Arc::new(evaluator));
254 self
255 }
256
257 /// Adds a custom SPARQL evaluation aggregate function.
258 ///
259 /// Note that it must also be given to the SPARQL parser using [`SparqlParser::with_custom_aggregate_function`](spargebra::SparqlParser::with_custom_aggregate_function).
260 ///
261 /// Example with a function doing concatenation:
262 /// ```
263 /// use oxrdf::{Dataset, Literal, NamedNode, Term};
264 /// use spareval::{AggregateFunctionAccumulator, QueryEvaluator, QueryResults};
265 /// use spargebra::SparqlParser;
266 /// use std::mem::take;
267 ///
268 /// struct ConcatAccumulator {
269 /// value: String,
270 /// }
271 ///
272 /// impl AggregateFunctionAccumulator for ConcatAccumulator {
273 /// fn accumulate(&mut self, element: Term) {
274 /// if let Term::Literal(v) = element {
275 /// if !self.value.is_empty() {
276 /// self.value.push(' ');
277 /// }
278 /// self.value.push_str(v.value());
279 /// }
280 /// }
281 ///
282 /// fn finish(&mut self) -> Option<Term> {
283 /// Some(Literal::new_simple_literal(take(&mut self.value)).into())
284 /// }
285 /// }
286 ///
287 /// let evaluator = QueryEvaluator::new().with_custom_aggregate_function(
288 /// NamedNode::new("http://example.com/concat")?,
289 /// || {
290 /// Box::new(ConcatAccumulator {
291 /// value: String::new(),
292 /// })
293 /// },
294 /// );
295 /// let query = SparqlParser::new()
296 /// .with_custom_aggregate_function(NamedNode::new("http://example.com/concat")?)
297 /// .parse_query(
298 /// "SELECT (<http://example.com/concat>(?v) AS ?r) WHERE { VALUES ?v { 1 2 3 } }",
299 /// )?;
300 /// if let QueryResults::Solutions(mut solutions) =
301 /// evaluator.prepare(&query).execute(&Dataset::new())?
302 /// {
303 /// assert_eq!(
304 /// solutions.next().unwrap()?.get("r"),
305 /// Some(&Literal::new_simple_literal("1 2 3").into())
306 /// );
307 /// }
308 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
309 /// ```
310 #[inline]
311 #[must_use]
312 pub fn with_custom_aggregate_function(
313 mut self,
314 name: NamedNode,
315 evaluator: impl Fn() -> Box<dyn AggregateFunctionAccumulator + Send + Sync>
316 + Send
317 + Sync
318 + 'static,
319 ) -> Self {
320 self.custom_aggregate_functions
321 .insert(name, Arc::new(evaluator));
322 self
323 }
324
325 /// Disables query optimizations and runs the query as it is.
326 #[inline]
327 #[must_use]
328 pub fn without_optimizations(mut self) -> Self {
329 self.without_optimizations = true;
330 self
331 }
332
333 /// Compute statistics during evaluation and fills them in the explanation tree.
334 #[inline]
335 #[must_use]
336 pub fn compute_statistics(mut self) -> Self {
337 self.run_stats = true;
338 self
339 }
340
341 /// Inject a cancellation token to the SPARQL evaluation.
342 ///
343 /// Might be used to abort a query cleanly.
344 ///
345 /// ```
346 /// use oxrdf::{Dataset, GraphName, NamedNode, Quad};
347 /// use spareval::{CancellationToken, QueryEvaluationError, QueryEvaluator, QueryResults};
348 /// use spargebra::SparqlParser;
349 ///
350 /// let ex = NamedNode::new("http://example.com")?;
351 /// let dataset = Dataset::from_iter([Quad::new(
352 /// ex.clone(),
353 /// ex.clone(),
354 /// ex.clone(),
355 /// GraphName::DefaultGraph,
356 /// )]);
357 /// let query = SparqlParser::new().parse_query("SELECT * WHERE { ?s ?p ?o }")?;
358 /// let cancellation_token = CancellationToken::new();
359 /// let evaluator = QueryEvaluator::new().with_cancellation_token(cancellation_token.clone());
360 /// let results = evaluator.prepare(&query).execute(&dataset)?;
361 /// if let QueryResults::Solutions(mut solutions) = results {
362 /// cancellation_token.cancel(); // We cancel
363 /// assert!(matches!(
364 /// solutions.next().unwrap().unwrap_err(), // It's cancelled
365 /// QueryEvaluationError::Cancelled
366 /// ));
367 /// }
368 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
369 /// ```
370 #[must_use]
371 pub fn with_cancellation_token(mut self, cancellation_token: CancellationToken) -> Self {
372 self.cancellation_token = Some(cancellation_token);
373 self
374 }
375
376 // Internal helper: evaluates a SPARQL expression to an ExpressionTerm against an empty dataset
377 fn eval_expression_term_with_substitutions<'a>(
378 &self,
379 expression: &sparopt::algebra::Expression,
380 substitutions: impl IntoIterator<Item = (&'a Variable, Term)>,
381 ) -> Option<ExpressionTerm> {
382 struct Context<'a> {
383 now: Option<DateTime>,
384 custom_functions: &'a CustomFunctionRegistry,
385 }
386
387 impl<'a> ExpressionEvaluatorContext<'a> for Context<'a> {
388 type Term = Term;
389 type Tuple = HashMap<&'a Variable, Term>;
390 type Error = QueryEvaluationError;
391
392 fn build_variable_lookup(
393 &mut self,
394 variable: &Variable,
395 ) -> impl Fn(&HashMap<&'a Variable, Term>) -> Option<Term> + 'a {
396 let variable = variable.clone();
397 move |tuple| tuple.get(&variable).cloned()
398 }
399
400 fn build_is_variable_bound(
401 &mut self,
402 variable: &Variable,
403 ) -> impl Fn(&HashMap<&'a Variable, Term>) -> bool + 'a {
404 let variable = variable.clone();
405 move |tuple| tuple.contains_key(&variable)
406 }
407
408 fn build_exists(
409 &mut self,
410 _: &GraphPattern,
411 ) -> Result<impl Fn(&HashMap<&'a Variable, Term>) -> bool + 'a, QueryEvaluationError>
412 {
413 Err::<fn(&HashMap<&'a Variable, Term>) -> bool, _>(
414 QueryEvaluationError::Unexpected(
415 "EXISTS is not supported by the SPARQL expression evaluator".into(),
416 ),
417 )
418 }
419
420 fn internalize_named_node(
421 &mut self,
422 term: &NamedNode,
423 ) -> Result<Term, QueryEvaluationError> {
424 Ok(term.clone().into())
425 }
426
427 fn internalize_literal(
428 &mut self,
429 term: &Literal,
430 ) -> Result<Term, QueryEvaluationError> {
431 Ok(term.clone().into())
432 }
433
434 fn build_internalize_expression_term(
435 &mut self,
436 ) -> impl Fn(ExpressionTerm) -> Option<Term> + 'a {
437 |t| Some(t.into())
438 }
439
440 fn build_externalize_expression_term(
441 &mut self,
442 ) -> impl Fn(Term) -> Option<ExpressionTerm> + 'a {
443 |t| Some(t.into())
444 }
445
446 fn now(&mut self) -> DateTime {
447 *self.now.get_or_insert_with(DateTime::now)
448 }
449
450 fn base_iri(&mut self) -> Option<Arc<Iri<String>>> {
451 None
452 }
453
454 fn custom_functions(&mut self) -> &CustomFunctionRegistry {
455 self.custom_functions
456 }
457 }
458
459 build_expression_evaluator(
460 expression,
461 &mut Context {
462 now: None,
463 custom_functions: &self.custom_functions,
464 },
465 )
466 .ok()?(&substitutions.into_iter().collect::<HashMap<_, _>>())
467 }
468
469 /// Evaluates a SPARQL expression against an empty dataset with optional variable substitutions.
470 ///
471 /// Returns the computed term or `None` if an error occurs or the expression is invalid.
472 pub fn evaluate_expression<'a>(
473 &self,
474 expression: &sparopt::algebra::Expression,
475 substitutions: impl IntoIterator<Item = (&'a Variable, Term)>,
476 ) -> Option<Term> {
477 self.eval_expression_term_with_substitutions(expression, substitutions)
478 .map(Into::into)
479 }
480
481 /// Evaluates a SPARQL expression effective boolean value (EBV) against an empty dataset
482 /// with optional variable substitutions.
483 ///
484 /// Returns the EBV or `None` if an error occurs or EBV is undefined for the result type.
485 pub fn evaluate_effective_boolean_value_expression<'a>(
486 &self,
487 expression: &sparopt::algebra::Expression,
488 substitutions: impl IntoIterator<Item = (&'a Variable, Term)>,
489 ) -> Option<bool> {
490 self.eval_expression_term_with_substitutions(expression, substitutions)?
491 .effective_boolean_value()
492 }
493
494 /// Evaluates a SPARQL UPDATE DELETE/INSERT operation.
495 ///
496 /// Returns the list of quads to delete or insert.
497 ///
498 /// ```
499 /// use oxrdf::{Dataset, GraphName, Literal, NamedNode, Quad};
500 /// use spareval::{DeleteInsertQuad, QueryEvaluator};
501 /// use spargebra::{GraphUpdateOperation, SparqlParser};
502 ///
503 /// let ex = NamedNode::new("http://example.com")?;
504 /// let dataset = Dataset::from_iter([Quad::new(
505 /// ex.clone(),
506 /// ex.clone(),
507 /// Literal::from(0),
508 /// GraphName::DefaultGraph,
509 /// )]);
510 /// let update = SparqlParser::new().parse_update(
511 /// "DELETE { ?s ?p ?o } INSERT { ?s ?p ?o2 } WHERE { ?s ?p ?o BIND(?o +1 AS ?o2) }",
512 /// )?;
513 /// let GraphUpdateOperation::DeleteInsert {
514 /// delete,
515 /// insert,
516 /// using: _,
517 /// pattern,
518 /// } = &update.operations[0]
519 /// else {
520 /// unreachable!()
521 /// };
522 /// let results = QueryEvaluator::new()
523 /// .prepare_delete_insert(delete.clone(), insert.clone(), None, None, pattern)
524 /// .execute(&dataset)?
525 /// .collect::<Result<Vec<_>, _>>()?;
526 /// assert_eq!(
527 /// results,
528 /// vec![
529 /// DeleteInsertQuad::Delete(Quad::new(
530 /// ex.clone(),
531 /// ex.clone(),
532 /// Literal::from(0),
533 /// GraphName::DefaultGraph,
534 /// )),
535 /// DeleteInsertQuad::Insert(Quad::new(
536 /// ex.clone(),
537 /// ex.clone(),
538 /// Literal::from(1),
539 /// GraphName::DefaultGraph,
540 /// ))
541 /// ]
542 /// );
543 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
544 /// ```
545 pub fn prepare_delete_insert<'a>(
546 &'a self,
547 delete: Vec<GroundQuadPattern>,
548 insert: Vec<QuadPattern>,
549 base_iri: Option<Iri<String>>,
550 using: Option<QueryDataset>,
551 pattern: &'a spargebra::algebra::GraphPattern,
552 ) -> PreparedDeleteInsertUpdate<'a> {
553 PreparedDeleteInsertUpdate {
554 evaluator: self,
555 pattern,
556 delete,
557 insert,
558 base_iri,
559 dataset: using.map(Into::into).unwrap_or_default(),
560 }
561 }
562
563 fn simple_evaluator<'a, D: QueryableDataset<'a>>(
564 &self,
565 dataset: D,
566 dataset_spec: QueryDatasetSpecification,
567 base_iri: &Option<Iri<String>>,
568 ) -> Result<SimpleEvaluator<'a, D>, QueryEvaluationError> {
569 SimpleEvaluator::new(
570 dataset,
571 base_iri.clone().map(Arc::new),
572 Rc::new(self.service_handler.clone()),
573 Rc::new(self.custom_functions.clone()),
574 Rc::new(self.custom_aggregate_functions.clone()),
575 self.cancellation_token.clone().unwrap_or_default(),
576 dataset_spec,
577 self.run_stats,
578 )
579 }
580}
581
582/// A prepared SPARQL query.
583///
584/// Allows customizing things like the evaluation dataset and substituting variables.
585///
586/// Usage example:
587/// ```
588/// use oxrdf::{Dataset, Literal, Variable};
589/// use spareval::{QueryEvaluator, QueryResults};
590/// use spargebra::SparqlParser;
591///
592/// let query = SparqlParser::new().parse_query("SELECT ?v WHERE {}")?;
593/// let evaluator = QueryEvaluator::new();
594/// let prepared_query = evaluator
595/// .prepare(&query)
596/// .substitute_variable(Variable::new("v")?, Literal::from(1));
597///
598/// if let QueryResults::Solutions(mut solutions) = prepared_query.execute(&Dataset::new())? {
599/// assert_eq!(
600/// solutions.next().unwrap()?.get("v"),
601/// Some(&Literal::from(1).into())
602/// );
603/// }
604/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
605/// ```
606#[derive(Clone)]
607#[must_use]
608pub struct PreparedQuery<'a> {
609 evaluator: &'a QueryEvaluator,
610 query: &'a Query,
611 dataset: QueryDatasetSpecification,
612 substitutions: HashMap<Variable, Term>,
613}
614
615impl PreparedQuery<'_> {
616 /// Substitute a variable with a given RDF term in the SPARQL query.
617 ///
618 /// Usage example:
619 /// ```
620 /// use oxrdf::{Dataset, Literal, Variable};
621 /// use spareval::{QueryEvaluator, QueryResults};
622 /// use spargebra::SparqlParser;
623 ///
624 /// let query = SparqlParser::new().parse_query("SELECT ?v WHERE {}")?;
625 /// let evaluator = QueryEvaluator::new();
626 /// let prepared_query = evaluator
627 /// .prepare(&query)
628 /// .substitute_variable(Variable::new("v")?, Literal::from(1));
629 ///
630 /// if let QueryResults::Solutions(mut solutions) = prepared_query.execute(&Dataset::new())? {
631 /// assert_eq!(
632 /// solutions.next().unwrap()?.get("v"),
633 /// Some(&Literal::from(1).into())
634 /// );
635 /// }
636 /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
637 /// ```
638 #[inline]
639 pub fn substitute_variable(
640 mut self,
641 variable: impl Into<Variable>,
642 term: impl Into<Term>,
643 ) -> Self {
644 self.substitutions.insert(variable.into(), term.into());
645 self
646 }
647
648 /// Returns [the query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset) of this prepared query.
649 #[inline]
650 pub fn dataset(&self) -> &QueryDatasetSpecification {
651 &self.dataset
652 }
653 /// Returns [the query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset) of this prepared query.
654 #[inline]
655 pub fn dataset_mut(&mut self) -> &mut QueryDatasetSpecification {
656 &mut self.dataset
657 }
658
659 /// Execute the SPARQL query against the given [`QueryableDataset`].
660 pub fn execute<'b>(
661 self,
662 dataset: impl QueryableDataset<'b>,
663 ) -> Result<QueryResults<'b>, QueryEvaluationError> {
664 self.explain(dataset).0
665 }
666
667 pub fn explain<'b>(
668 self,
669 dataset: impl QueryableDataset<'b>,
670 ) -> (
671 Result<QueryResults<'b>, QueryEvaluationError>,
672 QueryExplanation,
673 ) {
674 let start_planning = Timer::now();
675 let (results, plan_node_with_stats, planning_duration) = match self.query {
676 Query::Select {
677 pattern, base_iri, ..
678 } => {
679 let mut pattern = GraphPattern::from(pattern);
680 if !self.evaluator.without_optimizations {
681 pattern = Optimizer::optimize_graph_pattern(pattern);
682 }
683 let planning_duration = start_planning.elapsed();
684 let (results, explanation) =
685 match self
686 .evaluator
687 .simple_evaluator(dataset, self.dataset, base_iri)
688 {
689 Ok(evaluator) => evaluator.evaluate_select(&pattern, self.substitutions),
690 Err(e) => (Err(e), Rc::new(EvalNodeWithStats::empty())),
691 };
692 (
693 results.map(QueryResults::Solutions),
694 explanation,
695 planning_duration,
696 )
697 }
698 Query::Ask {
699 pattern, base_iri, ..
700 } => {
701 let mut pattern = GraphPattern::from(pattern);
702 if !self.evaluator.without_optimizations {
703 pattern = Optimizer::optimize_graph_pattern(pattern);
704 }
705 let planning_duration = start_planning.elapsed();
706 let (results, explanation) =
707 match self
708 .evaluator
709 .simple_evaluator(dataset, self.dataset, base_iri)
710 {
711 Ok(evaluator) => evaluator.evaluate_ask(&pattern, self.substitutions),
712 Err(e) => (Err(e), Rc::new(EvalNodeWithStats::empty())),
713 };
714 (
715 results.map(QueryResults::Boolean),
716 explanation,
717 planning_duration,
718 )
719 }
720 Query::Construct {
721 template,
722 pattern,
723 base_iri,
724 ..
725 } => {
726 let mut pattern = GraphPattern::from(pattern);
727 if !self.evaluator.without_optimizations {
728 pattern = Optimizer::optimize_graph_pattern(pattern);
729 }
730 let planning_duration = start_planning.elapsed();
731 let (results, explanation) =
732 match self
733 .evaluator
734 .simple_evaluator(dataset, self.dataset, base_iri)
735 {
736 Ok(evaluator) => {
737 evaluator.evaluate_construct(&pattern, template, self.substitutions)
738 }
739 Err(e) => (Err(e), Rc::new(EvalNodeWithStats::empty())),
740 };
741 (
742 results.map(QueryResults::Graph),
743 explanation,
744 planning_duration,
745 )
746 }
747 Query::Describe {
748 pattern, base_iri, ..
749 } => {
750 let mut pattern = GraphPattern::from(pattern);
751 if !self.evaluator.without_optimizations {
752 pattern = Optimizer::optimize_graph_pattern(pattern);
753 }
754 let planning_duration = start_planning.elapsed();
755 let (results, explanation) =
756 match self
757 .evaluator
758 .simple_evaluator(dataset, self.dataset, base_iri)
759 {
760 Ok(evaluator) => evaluator.evaluate_describe(&pattern, self.substitutions),
761 Err(e) => (Err(e), Rc::new(EvalNodeWithStats::empty())),
762 };
763 (
764 results.map(QueryResults::Graph),
765 explanation,
766 planning_duration,
767 )
768 }
769 };
770 let explanation = QueryExplanation {
771 inner: plan_node_with_stats,
772 with_stats: self.evaluator.run_stats,
773 planning_duration,
774 };
775 (results, explanation)
776 }
777}
778
779/// A prepared SPARQL query.
780///
781/// Allows customizing things like the evaluation dataset and substituting variables.
782///
783/// Usage example:
784/// ```
785/// use oxrdf::{Dataset, GraphName, Literal, NamedNode, Quad};
786/// use spareval::{DeleteInsertQuad, QueryEvaluator};
787/// use spargebra::{GraphUpdateOperation, SparqlParser};
788///
789/// let ex = NamedNode::new("http://example.com")?;
790/// let dataset = Dataset::from_iter([Quad::new(
791/// ex.clone(),
792/// ex.clone(),
793/// Literal::from(0),
794/// GraphName::DefaultGraph,
795/// )]);
796/// let update = SparqlParser::new().parse_update(
797/// "DELETE { ?s ?p ?o } INSERT { ?s ?p ?o2 } WHERE { ?s ?p ?o BIND(?o +1 AS ?o2) }",
798/// )?;
799/// let GraphUpdateOperation::DeleteInsert {
800/// delete,
801/// insert,
802/// using: _,
803/// pattern,
804/// } = &update.operations[0]
805/// else {
806/// unreachable!()
807/// };
808/// let results = QueryEvaluator::new()
809/// .prepare_delete_insert(delete.clone(), insert.clone(), None, None, pattern)
810/// .execute(&dataset)?
811/// .collect::<Result<Vec<_>, _>>()?;
812/// assert_eq!(
813/// results,
814/// vec![
815/// DeleteInsertQuad::Delete(Quad::new(
816/// ex.clone(),
817/// ex.clone(),
818/// Literal::from(0),
819/// GraphName::DefaultGraph,
820/// )),
821/// DeleteInsertQuad::Insert(Quad::new(
822/// ex.clone(),
823/// ex.clone(),
824/// Literal::from(1),
825/// GraphName::DefaultGraph,
826/// ))
827/// ]
828/// );
829/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
830/// ```
831#[derive(Clone)]
832#[must_use]
833pub struct PreparedDeleteInsertUpdate<'a> {
834 evaluator: &'a QueryEvaluator,
835 pattern: &'a spargebra::algebra::GraphPattern,
836 delete: Vec<GroundQuadPattern>,
837 insert: Vec<QuadPattern>,
838 base_iri: Option<Iri<String>>,
839 dataset: QueryDatasetSpecification,
840}
841
842impl PreparedDeleteInsertUpdate<'_> {
843 /// Returns [the query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset) of this prepared update.
844 #[inline]
845 pub fn dataset(&self) -> &QueryDatasetSpecification {
846 &self.dataset
847 }
848 /// Returns [the query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset) of this prepared update.
849 #[inline]
850 pub fn dataset_mut(&mut self) -> &mut QueryDatasetSpecification {
851 &mut self.dataset
852 }
853
854 /// Execute the SPARQL query against the given [`QueryableDataset`].
855 pub fn execute<'b>(
856 self,
857 dataset: impl QueryableDataset<'b>,
858 ) -> Result<DeleteInsertIter<'b>, QueryEvaluationError> {
859 let mut pattern = GraphPattern::from(self.pattern);
860 if !self.evaluator.without_optimizations {
861 pattern = Optimizer::optimize_graph_pattern(pattern);
862 }
863 let (solutions, _) = self
864 .evaluator
865 .simple_evaluator(dataset, self.dataset, &self.base_iri)?
866 .evaluate_select(&pattern, []);
867 Ok(DeleteInsertIter::new(solutions?, self.delete, self.insert))
868 }
869}
870
871pub(crate) type CustomAggregateFunctionRegistry = HashMap<
872 NamedNode,
873 Arc<dyn (Fn() -> Box<dyn AggregateFunctionAccumulator + Send + Sync>) + Send + Sync>,
874>;
875
876/// A trait for custom aggregate function implementation.
877///
878/// The accumulator accumulates values using the [`accumulate`](Self::accumulate) method
879/// and returns a final aggregated value (or an error) using [`finish`](Self::finish).
880///
881/// See [`QueryEvaluator::with_custom_aggregate_function`] for an example.
882pub trait AggregateFunctionAccumulator {
883 fn accumulate(&mut self, element: Term);
884 fn finish(&mut self) -> Option<Term>;
885}
886
887/// An extended SPARQL query [dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset).
888///
889/// Allows setting blank node graph names and that the default graph is the union of all named graphs.
890#[derive(Eq, PartialEq, Debug, Clone, Hash)]
891pub struct QueryDatasetSpecification {
892 default: Option<Vec<GraphName>>,
893 named: Option<Vec<NamedOrBlankNode>>,
894}
895
896impl QueryDatasetSpecification {
897 pub fn new() -> Self {
898 Self {
899 default: Some(vec![GraphName::DefaultGraph]),
900 named: None,
901 }
902 }
903
904 /// Checks if this dataset specification is the default one
905 /// (i.e., the default graph is the store default graph, and all named graphs included in the queried store are available)
906 pub fn is_default_dataset(&self) -> bool {
907 // TODO: rename to is_default?
908 self.default
909 .as_ref()
910 .is_some_and(|t| t == &[GraphName::DefaultGraph])
911 && self.named.is_none()
912 }
913
914 /// Returns the list of the store graphs that are available to the query as the default graph or `None` if the union of all graphs is used as the default graph.
915 /// This list is by default only the store default graph.
916 pub fn default_graph_graphs(&self) -> Option<&[GraphName]> {
917 self.default.as_deref()
918 }
919
920 /// Sets the default graph of the query to be the union of all the graphs in the queried store.
921 ///
922 /// ```
923 /// use oxrdf::{Dataset, NamedNode, Quad};
924 /// use spareval::{QueryEvaluator, QueryResults};
925 /// use spargebra::SparqlParser;
926 ///
927 /// let dataset = Dataset::from_iter([Quad::new(
928 /// NamedNode::new("http://example.com/s")?,
929 /// NamedNode::new("http://example.com/p")?,
930 /// NamedNode::new("http://example.com/o")?,
931 /// NamedNode::new("http://example.com/g")?,
932 /// )]);
933 /// let query = SparqlParser::new().parse_query("SELECT * WHERE { ?s ?p ?o }")?;
934 /// let evaluator = QueryEvaluator::new();
935 /// let mut prepared = evaluator.prepare(&query);
936 /// prepared
937 /// .dataset_mut()
938 /// .set_default_graph(vec![NamedNode::new("http://example.com/g")?.into()]);
939 /// if let QueryResults::Solutions(mut solutions) = prepared.execute(&dataset)? {
940 /// assert_eq!(
941 /// solutions.next().unwrap()?.get("s"),
942 /// Some(&NamedNode::new("http://example.com/s")?.into())
943 /// );
944 /// }
945 ///
946 /// # Ok::<_, Box<dyn std::error::Error>>(())
947 /// ```
948 pub fn set_default_graph_as_union(&mut self) {
949 self.default = None;
950 }
951
952 /// Sets the list of graphs the query should consider as being part of the default graph.
953 ///
954 /// By default, only the store default graph is considered.
955 /// ```
956 /// use oxrdf::{Dataset, NamedNode, Quad};
957 /// use spareval::{QueryEvaluator, QueryResults};
958 /// use spargebra::SparqlParser;
959 ///
960 /// let dataset = Dataset::from_iter([Quad::new(
961 /// NamedNode::new("http://example.com/s")?,
962 /// NamedNode::new("http://example.com/p")?,
963 /// NamedNode::new("http://example.com/o")?,
964 /// NamedNode::new("http://example.com/g")?,
965 /// )]);
966 /// let query = SparqlParser::new().parse_query("SELECT * WHERE { ?s ?p ?o }")?;
967 /// let evaluator = QueryEvaluator::new();
968 /// let mut prepared = evaluator.prepare(&query);
969 /// prepared
970 /// .dataset_mut()
971 /// .set_default_graph(vec![NamedNode::new("http://example.com/g")?.into()]);
972 /// if let QueryResults::Solutions(mut solutions) = prepared.execute(&dataset)? {
973 /// assert_eq!(
974 /// solutions.next().unwrap()?.get("s"),
975 /// Some(&NamedNode::new("http://example.com/s")?.into())
976 /// );
977 /// }
978 ///
979 /// # Ok::<_, Box<dyn std::error::Error>>(())
980 /// ```
981 pub fn set_default_graph(&mut self, graphs: Vec<GraphName>) {
982 self.default = Some(graphs)
983 }
984
985 /// Returns the list of the available named graphs for the query or `None` if all graphs are available
986 pub fn available_named_graphs(&self) -> Option<&[NamedOrBlankNode]> {
987 self.named.as_deref()
988 }
989
990 /// Sets the list of allowed named graphs in the query.
991 ///
992 /// ```
993 /// use oxrdf::{Dataset, NamedNode, Quad};
994 /// use spareval::{QueryEvaluator, QueryResults};
995 /// use spargebra::SparqlParser;
996 ///
997 /// let dataset = Dataset::from_iter([Quad::new(
998 /// NamedNode::new("http://example.com/s")?,
999 /// NamedNode::new("http://example.com/p")?,
1000 /// NamedNode::new("http://example.com/o")?,
1001 /// NamedNode::new("http://example.com/g")?,
1002 /// )]);
1003 /// let query = SparqlParser::new().parse_query("SELECT * WHERE { ?s ?p ?o }")?;
1004 /// let evaluator = QueryEvaluator::new();
1005 /// let mut prepared = evaluator.prepare(&query);
1006 /// prepared
1007 /// .dataset_mut()
1008 /// .set_available_named_graphs(Vec::new());
1009 /// if let QueryResults::Solutions(mut solutions) = prepared.execute(&dataset)? {
1010 /// assert!(solutions.next().is_none(),);
1011 /// }
1012 ///
1013 /// # Ok::<_, Box<dyn std::error::Error>>(())
1014 /// ```
1015 pub fn set_available_named_graphs(&mut self, named_graphs: Vec<NamedOrBlankNode>) {
1016 self.named = Some(named_graphs);
1017 }
1018}
1019
1020impl Default for QueryDatasetSpecification {
1021 fn default() -> Self {
1022 Self::new()
1023 }
1024}
1025
1026impl From<QueryDataset> for QueryDatasetSpecification {
1027 fn from(dataset: QueryDataset) -> Self {
1028 Self {
1029 default: Some(dataset.default.into_iter().map(Into::into).collect()),
1030 named: dataset
1031 .named
1032 .map(|named| named.into_iter().map(Into::into).collect()),
1033 }
1034 }
1035}
1036
1037/// The explanation of a query.
1038#[derive(Clone)]
1039pub struct QueryExplanation {
1040 inner: Rc<EvalNodeWithStats>,
1041 with_stats: bool,
1042 planning_duration: Option<DayTimeDuration>,
1043}
1044
1045impl QueryExplanation {
1046 /// Writes the explanation as JSON.
1047 pub fn write_in_json(&self, writer: impl io::Write) -> io::Result<()> {
1048 let mut serializer = WriterJsonSerializer::new(writer);
1049 serializer.serialize_event(JsonEvent::StartObject)?;
1050 if let Some(planning_duration) = self.planning_duration {
1051 serializer
1052 .serialize_event(JsonEvent::ObjectKey("planning duration in seconds".into()))?;
1053 serializer.serialize_event(JsonEvent::Number(
1054 planning_duration.as_seconds().to_string().into(),
1055 ))?;
1056 }
1057 serializer.serialize_event(JsonEvent::ObjectKey("plan".into()))?;
1058 self.inner.json_node(&mut serializer, self.with_stats)?;
1059 serializer.serialize_event(JsonEvent::EndObject)
1060 }
1061}
1062
1063impl fmt::Debug for QueryExplanation {
1064 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1065 let mut obj = f.debug_struct("QueryExplanation");
1066 if let Some(planning_duration) = self.planning_duration {
1067 obj.field(
1068 "planning duration in seconds",
1069 &f32::from(Float::from(planning_duration.as_seconds())),
1070 );
1071 }
1072 obj.field("tree", &self.inner);
1073 obj.finish_non_exhaustive()
1074 }
1075}
1076
1077#[cfg(test)]
1078mod tests {
1079 use super::*;
1080 use oxrdf::vocab::xsd;
1081 use oxrdf::{Literal, Term};
1082 use sparopt::algebra::{Expression, GraphPattern};
1083
1084 #[test]
1085 fn evaluate_expression_literal_and_arithmetic() {
1086 let evaluator = QueryEvaluator::new();
1087
1088 // Simple literal
1089 let expr = Expression::from(Literal::from(3_i32));
1090 let term = evaluator.evaluate_expression(&expr, std::iter::empty());
1091 assert_eq!(term, Some(Term::from(Literal::from(3_i32))));
1092
1093 // 1 + 2 = 3
1094 let add = Expression::Add(
1095 Box::new(Expression::from(Literal::from(1_i32))),
1096 Box::new(Expression::from(Literal::from(2_i32))),
1097 );
1098 let term = evaluator.evaluate_expression(&add, std::iter::empty());
1099 assert_eq!(term, Some(Term::from(Literal::from(3_i32))));
1100 }
1101
1102 #[test]
1103 fn evaluate_expression_with_variable_substitution() {
1104 let evaluator = QueryEvaluator::new();
1105 let x = Variable::new("x").unwrap();
1106
1107 // ?x + 2 with ?x = 1 => 3
1108 let expr = Expression::Add(
1109 Box::new(Expression::from(x.clone())),
1110 Box::new(Expression::from(Literal::from(2_i32))),
1111 );
1112 let one: Term = Literal::from(1_i32).into();
1113 let result = evaluator.evaluate_expression(&expr, [(&x, one)]);
1114 assert_eq!(result, Some(Term::from(Literal::from(3_i32))));
1115 }
1116
1117 #[test]
1118 fn evaluate_expression_with_unbound_variable_returns_none() {
1119 let evaluator = QueryEvaluator::new();
1120 let x = Variable::new("x").unwrap();
1121 let expr = Expression::from(x);
1122 let result = evaluator.evaluate_expression(&expr, std::iter::empty());
1123 assert!(result.is_none());
1124 }
1125
1126 #[test]
1127 fn evaluate_effective_boolean_value_expression_basic() {
1128 let evaluator = QueryEvaluator::new();
1129
1130 // Numeric EBV: 0 -> false, non-zero -> true
1131 let zero = Expression::from(Literal::from(0_i32));
1132 let five = Expression::from(Literal::from(5_i32));
1133 assert_eq!(
1134 evaluator.evaluate_effective_boolean_value_expression(&zero, std::iter::empty()),
1135 Some(false)
1136 );
1137 assert_eq!(
1138 evaluator.evaluate_effective_boolean_value_expression(&five, std::iter::empty()),
1139 Some(true)
1140 );
1141
1142 // String EBV: empty -> false, non-empty -> true
1143 let empty_str = Expression::from(Literal::from(""));
1144 let non_empty_str = Expression::from(Literal::from("a"));
1145 assert_eq!(
1146 evaluator.evaluate_effective_boolean_value_expression(&empty_str, std::iter::empty()),
1147 Some(false)
1148 );
1149 assert_eq!(
1150 evaluator
1151 .evaluate_effective_boolean_value_expression(&non_empty_str, std::iter::empty()),
1152 Some(true)
1153 );
1154 }
1155
1156 #[test]
1157 fn evaluate_effective_boolean_value_expression_exists() {
1158 let evaluator = QueryEvaluator::new();
1159
1160 // EXISTS {} (empty) -> false
1161 let exists_empty = Expression::exists(GraphPattern::empty());
1162 assert_eq!(
1163 evaluator
1164 .evaluate_effective_boolean_value_expression(&exists_empty, std::iter::empty()),
1165 Some(false)
1166 );
1167
1168 // EXISTS { VALUES () {} } (empty singleton) -> true
1169 let exists_unit = Expression::exists(GraphPattern::empty_singleton());
1170 assert_eq!(
1171 evaluator.evaluate_effective_boolean_value_expression(&exists_unit, std::iter::empty()),
1172 Some(true)
1173 );
1174 }
1175
1176 #[test]
1177 fn evaluate_effective_boolean_value_expression_non_boolean_term() {
1178 let evaluator = QueryEvaluator::new();
1179
1180 // NamedNode has no EBV
1181 let iri = NamedNode::new("http://example.com/").unwrap();
1182 let nn = Expression::from(iri.clone());
1183 assert_eq!(
1184 evaluator.evaluate_effective_boolean_value_expression(&nn, std::iter::empty()),
1185 None
1186 );
1187
1188 // dateTime literal has no EBV
1189 let dt = Literal::new_typed_literal("2020-01-01T00:00:00Z", xsd::DATE_TIME);
1190 let expr = Expression::from(dt);
1191 assert_eq!(
1192 evaluator.evaluate_effective_boolean_value_expression(&expr, std::iter::empty()),
1193 None
1194 );
1195 }
1196
1197 #[test]
1198 fn evaluate_effective_boolean_value_expression_boolean_lexical_forms() {
1199 let evaluator = QueryEvaluator::new();
1200 let one = Expression::from(Literal::new_typed_literal("1", xsd::BOOLEAN));
1201 let zero = Expression::from(Literal::new_typed_literal("0", xsd::BOOLEAN));
1202 assert_eq!(
1203 evaluator.evaluate_effective_boolean_value_expression(&one, std::iter::empty()),
1204 Some(true)
1205 );
1206 assert_eq!(
1207 evaluator.evaluate_effective_boolean_value_expression(&zero, std::iter::empty()),
1208 Some(false)
1209 );
1210 }
1211
1212 #[test]
1213 fn evaluate_effective_boolean_value_expression_logic_with_errors() {
1214 let evaluator = QueryEvaluator::new();
1215
1216 // OR(error, false) => error (None)
1217 let errorish = Expression::from(NamedNode::new("http://e/iri").unwrap());
1218 let or_expr = Expression::or_all([errorish, Expression::from(Literal::from(false))]);
1219 assert_eq!(
1220 evaluator.evaluate_effective_boolean_value_expression(&or_expr, std::iter::empty()),
1221 None
1222 );
1223
1224 // AND(false, error) => false
1225 let errorish = Expression::from(NamedNode::new("http://e/iri2").unwrap());
1226 let and_expr = Expression::and_all([Expression::from(Literal::from(false)), errorish]);
1227 assert_eq!(
1228 evaluator.evaluate_effective_boolean_value_expression(&and_expr, std::iter::empty()),
1229 Some(false)
1230 );
1231
1232 // AND(true, error) => error (None)
1233 let errorish = Expression::from(NamedNode::new("http://e/iri3").unwrap());
1234 let and_expr = Expression::and_all([Expression::from(Literal::from(true)), errorish]);
1235 assert_eq!(
1236 evaluator.evaluate_effective_boolean_value_expression(&and_expr, std::iter::empty()),
1237 None
1238 );
1239 }
1240
1241 #[test]
1242 fn evaluate_expression_equality_returns_boolean_literal() {
1243 let evaluator = QueryEvaluator::new();
1244 let eq = Expression::equal(
1245 Expression::from(Literal::from(1_i32)),
1246 Expression::from(Literal::from(1_i32)),
1247 );
1248 let term = evaluator.evaluate_expression(&eq, std::iter::empty());
1249 assert_eq!(term, Some(Term::from(Literal::from(true))));
1250 }
1251
1252 #[test]
1253 fn evaluate_expression_arithmetic_with_unbound_variable_is_none() {
1254 let evaluator = QueryEvaluator::new();
1255 let x = Variable::new("x").unwrap();
1256 let expr = Expression::Add(
1257 Box::new(Expression::from(Literal::from(2_i32))),
1258 Box::new(Expression::from(x)),
1259 );
1260 let result = evaluator.evaluate_expression(&expr, std::iter::empty());
1261 assert!(result.is_none());
1262 }
1263}