1use crate::frozen::{FrozenIndexedDataset, TermId};
23use crate::native_exec;
24use crate::profile;
25use crate::validate::UnsupportedPolicy;
26use oxigraph::sparql::{PreparedSparqlQuery, QueryResults, SparqlEvaluator};
27use oxigraph::store::Store;
28use oxrdf::{
29 Graph, GraphName, GraphNameRef, Literal, NamedNode, NamedOrBlankNode, Quad, QuadRef, Term,
30 Triple, Variable,
31};
32use serde::{Deserialize, Serialize};
33use shifty_algebra::{Path, SparqlConstraint};
34use shifty_opt::{NativeQueryPlan, QueryForm, lower_query_with_stats};
35use spargebra::algebra::{
36 AggregateExpression, Expression, GraphPattern, OrderExpression, PropertyPathExpression,
37};
38use spargebra::term::{NamedNodePattern, TermPattern, TriplePattern};
39use spargebra::{Query, SparqlParser};
40use std::cell::RefCell;
41use std::collections::{HashMap, HashSet};
42use std::rc::Rc;
43
44pub(crate) use crate::frozen::SHAPES_GRAPH_IRI;
47
48pub(crate) struct SparqlExecutor {
49 store: Option<Store>,
52 frozen: Option<FrozenIndexedDataset>,
55 prepared: RefCell<HashMap<String, PreparedSparqlQuery>>,
56 parsed: RefCell<HashMap<String, Query>>,
57 compiled: RefCell<HashMap<CompileKey, Rc<Compiled>>>,
61 constructs: RefCell<HashMap<String, Rc<CompiledConstruct>>>,
63 batch_construct: RefCell<HashSet<String>>,
68 shapes_graph: Option<NamedNode>,
71 functions: Vec<FunctionDef>,
75 unsupported: UnsupportedPolicy,
77}
78
79#[derive(Clone)]
88pub(crate) struct FunctionDef {
89 pub iri: NamedNode,
90 pub params: Vec<String>,
92 pub query: String,
94 pub reads_graph: bool,
99}
100
101#[derive(PartialEq, Eq, Hash)]
105struct CompileKey {
106 query: String,
107 path: Option<String>,
108 shape: Option<String>,
109}
110
111struct Compiled {
116 plan: Option<NativeQueryPlan>,
117 query: Query,
118 fallback_reason: Option<String>,
125 batched: RefCell<Option<BatchedResults>>,
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
138pub struct SparqlDiagnostic {
139 pub query: String,
144 pub bindings: Vec<(String, Term)>,
148 pub results: Vec<Vec<(String, Term)>>,
154 pub fallback_reason: Option<String>,
159}
160
161struct BatchedResults {
166 covered: HashSet<Term>,
167 map: HashMap<Term, Vec<SparqlViolation>>,
168}
169
170struct CompiledConstruct {
171 plan: Option<NativeQueryPlan>,
172 template: Vec<TriplePattern>,
173}
174
175#[derive(Debug, Clone)]
176pub(crate) struct SparqlViolation {
177 pub value: Option<Term>,
178 pub path: Option<Term>,
179 pub message: Option<Term>,
183 pub bindings: HashMap<String, Term>,
186}
187
188impl SparqlExecutor {
189 pub fn new(graph: &Graph) -> Result<Self, String> {
190 Self::build(graph, None)
191 }
192
193 fn build(context: &Graph, shapes: Option<&Graph>) -> Result<Self, String> {
194 let store = Store::new().map_err(|e| e.to_string())?;
195 store
196 .extend(context.iter().map(|triple| {
197 Quad::new(
198 triple.subject.into_owned(),
199 triple.predicate.into_owned(),
200 triple.object.into_owned(),
201 GraphName::DefaultGraph,
202 )
203 }))
204 .map_err(|e| e.to_string())?;
205 let shapes_graph = match shapes {
206 Some(shapes) => {
207 let iri = NamedNode::new(SHAPES_GRAPH_IRI).expect("static IRI is valid");
208 store
209 .extend(shapes.iter().map(|triple| {
210 Quad::new(
211 triple.subject.into_owned(),
212 triple.predicate.into_owned(),
213 triple.object.into_owned(),
214 GraphName::NamedNode(iri.clone()),
215 )
216 }))
217 .map_err(|e| e.to_string())?;
218 Some(iri)
219 }
220 None => None,
221 };
222 Ok(Self {
223 store: Some(store),
224 frozen: None,
225 prepared: RefCell::new(HashMap::new()),
226 parsed: RefCell::new(HashMap::new()),
227 compiled: RefCell::new(HashMap::new()),
228 constructs: RefCell::new(HashMap::new()),
229 batch_construct: RefCell::new(HashSet::new()),
230 shapes_graph,
231 functions: Vec::new(),
232 unsupported: UnsupportedPolicy::default(),
233 })
234 }
235
236 pub fn from_frozen(frozen: FrozenIndexedDataset, has_shapes_graph: bool) -> Self {
237 Self {
238 store: None,
239 frozen: Some(frozen),
240 prepared: RefCell::new(HashMap::new()),
241 parsed: RefCell::new(HashMap::new()),
242 compiled: RefCell::new(HashMap::new()),
243 constructs: RefCell::new(HashMap::new()),
244 batch_construct: RefCell::new(HashSet::new()),
245 shapes_graph: has_shapes_graph
246 .then(|| NamedNode::new(SHAPES_GRAPH_IRI).expect("static IRI is valid")),
247 functions: Vec::new(),
248 unsupported: UnsupportedPolicy::default(),
249 }
250 }
251
252 pub(crate) fn set_functions(&mut self, functions: Vec<FunctionDef>, policy: UnsupportedPolicy) {
257 self.functions = functions;
258 self.unsupported = policy;
259 }
260
261 fn evaluator(&self) -> SparqlEvaluator {
271 let mut evaluator = SparqlEvaluator::new();
272 for f in &self.functions {
273 if f.reads_graph && self.unsupported == UnsupportedPolicy::Error {
274 continue;
275 }
276 let query = f.query.clone();
277 let params = f.params.clone();
278 evaluator = evaluator.with_custom_function(f.iri.clone(), move |args| {
279 eval_sparql_function(&query, ¶ms, args)
280 });
281 }
282 evaluator
283 }
284
285 pub(crate) fn evaluate_expression(
289 &self,
290 prologue: &str,
291 expr: &str,
292 ) -> Result<Option<Term>, String> {
293 let wrapped = format!("{prologue}\nSELECT (({expr}) AS ?result) WHERE {{}}");
294 let query = SparqlParser::new().parse_query(&wrapped).map_err(err)?;
295 let results = if let Some(frozen) = &self.frozen {
296 self.evaluator()
297 .for_query(query)
298 .on_queryable_dataset(frozen)
299 .execute()
300 .map_err(err)?
301 } else {
302 self.evaluator()
303 .for_query(query)
304 .on_store(self.store()?)
305 .execute()
306 .map_err(err)?
307 };
308 match results {
309 QueryResults::Solutions(mut solutions) => Ok(solutions
310 .next()
311 .transpose()
312 .map_err(err)?
313 .and_then(|s| s.get("result").cloned())),
314 _ => Err("expression did not produce solutions".to_string()),
315 }
316 }
317
318 pub(crate) fn frozen(&self) -> Option<&FrozenIndexedDataset> {
322 self.frozen.as_ref()
323 }
324
325 pub fn insert(&self, triple: &Triple) -> Result<(), String> {
326 self.store()?
327 .insert(QuadRef::new(
328 triple.subject.as_ref(),
329 triple.predicate.as_ref(),
330 triple.object.as_ref(),
331 GraphNameRef::DefaultGraph,
332 ))
333 .map_err(|e| e.to_string())
334 }
335
336 pub fn target_nodes(&self, query: &str) -> Result<Vec<Term>, String> {
337 let fp = profile::fingerprint(query);
338 let start = web_time::Instant::now();
339 let results = if let Some(frozen) = &self.frozen {
340 self.prepared(query)?
341 .on_queryable_dataset(frozen)
342 .execute()
343 .map_err(err)?
344 } else {
345 self.prepared(query)?
346 .on_store(self.store()?)
347 .execute()
348 .map_err(err)?
349 };
350 let QueryResults::Solutions(solutions) = results else {
351 return Err("SPARQL target did not produce SELECT solutions".to_string());
352 };
353 let mut nodes = Vec::new();
354 for solution in solutions {
355 if let Some(node) = solution.map_err(err)?.get("this") {
356 nodes.push(node.clone());
357 }
358 }
359 profile::record(
360 &fp,
361 start.elapsed().as_micros() as u64,
362 profile::ExecutorKind::Fallback { reason: None },
363 );
364 Ok(nodes)
365 }
366
367 pub fn constraint_violations(
368 &self,
369 constraint: &SparqlConstraint,
370 focus: &Term,
371 ) -> Result<Vec<SparqlViolation>, String> {
372 let compiled = self.compile_constraint(constraint)?;
373 let fp = profile::fingerprint(&constraint.query);
374 let start = web_time::Instant::now();
375
376 if let Some(batched) = compiled.batched.borrow().as_ref()
380 && batched.covered.contains(focus)
381 {
382 let violations = batched.map.get(focus).cloned().unwrap_or_default();
383 profile::record(
384 &fp,
385 start.elapsed().as_micros() as u64,
386 profile::ExecutorKind::Fallback { reason: None },
387 );
388 return Ok(violations);
389 }
390
391 if let Some(plan) = &compiled.plan {
392 let violations = self.run_native(plan, focus);
393 #[cfg(debug_assertions)]
397 {
398 let reference = self.run_fallback(&compiled.query, focus)?;
399 assert_violations_match(&constraint.query, focus, &violations, &reference);
400 }
401 profile::record(
402 &fp,
403 start.elapsed().as_micros() as u64,
404 profile::ExecutorKind::Native,
405 );
406 Ok(violations)
407 } else {
408 let result = self.run_fallback(&compiled.query, focus)?;
409 profile::record(
410 &fp,
411 start.elapsed().as_micros() as u64,
412 profile::ExecutorKind::Fallback { reason: None },
413 );
414 Ok(result)
415 }
416 }
417
418 fn compile_constraint(&self, constraint: &SparqlConstraint) -> Result<Rc<Compiled>, String> {
423 let key = CompileKey {
424 query: constraint.query.clone(),
425 path: constraint.path.as_ref().map(path_key),
426 shape: constraint.shape.as_ref().map(|s| s.to_string()),
427 };
428 if let Some(compiled) = self.compiled.borrow().get(&key) {
429 return Ok(compiled.clone());
430 }
431
432 let mut query = self.parse(&constraint.query)?;
436 if let Some(path) = &constraint.path {
437 query = apply_path_prebinding(query, path)?;
438 }
439 if let Some(shape) = &constraint.shape {
440 substitute_query(&mut query, &variable("currentShape"), shape);
441 }
442 if let Some(graph) = &self.shapes_graph {
443 substitute_query(
444 &mut query,
445 &variable("shapesGraph"),
446 &Term::NamedNode(graph.clone()),
447 );
448 }
449 for (name, value) in &constraint.extra_bindings {
452 substitute_query(&mut query, &variable(name), value);
453 }
454 if constraint.bind_value_to_this {
457 rename_variable_in_query(&mut query, &variable("value"), &variable("this"));
458 }
459
460 let (plan, fallback_reason) = match &self.frozen {
462 Some(frozen) => match lower_query_with_stats(&query, Some(&frozen.plan_stats())) {
463 Ok(plan) => (Some(plan), None),
464 Err(reason) => (None, Some(reason)),
465 },
466 None => (None, None),
467 };
468 let compiled = Rc::new(Compiled {
469 plan,
470 query,
471 fallback_reason,
472 batched: RefCell::new(None),
473 });
474 self.compiled.borrow_mut().insert(key, compiled.clone());
475 Ok(compiled)
476 }
477
478 pub fn constraint_diagnostic(
487 &self,
488 constraint: &SparqlConstraint,
489 focus: &Term,
490 violations: &[SparqlViolation],
491 ) -> Result<SparqlDiagnostic, String> {
492 let compiled = self.compile_constraint(constraint)?;
493 let mut bindings = vec![("this".to_string(), focus.clone())];
494 if let Some(shape) = &constraint.shape {
495 bindings.push(("currentShape".to_string(), shape.clone()));
496 }
497 if let Some(graph) = &self.shapes_graph {
498 bindings.push(("shapesGraph".to_string(), Term::NamedNode(graph.clone())));
499 }
500 bindings.extend(constraint.extra_bindings.iter().cloned());
501 let results = violations
502 .iter()
503 .map(|v| {
504 let mut row: Vec<(String, Term)> = v
505 .bindings
506 .iter()
507 .map(|(k, t)| (k.clone(), t.clone()))
508 .collect();
509 row.sort_by(|a, b| a.0.cmp(&b.0));
510 row
511 })
512 .collect();
513 Ok(SparqlDiagnostic {
514 query: compiled.query.to_string(),
515 bindings,
516 results,
517 fallback_reason: compiled.fallback_reason.clone(),
518 })
519 }
520
521 fn run_native(&self, plan: &NativeQueryPlan, focus: &Term) -> Vec<SparqlViolation> {
524 let frozen = self
525 .frozen
526 .as_ref()
527 .expect("native plan implies a frozen dataset (compile_constraint guards this)");
528 let foci = [focus.clone()];
529 let result = native_exec::execute(plan, frozen, &foci);
530 let solutions = &result.solutions[0];
531 match result.form {
532 QueryForm::Ask => {
534 if solutions.is_empty() {
535 Vec::new()
536 } else {
537 vec![SparqlViolation {
538 value: None,
539 path: None,
540 message: None,
541 bindings: HashMap::new(),
542 }]
543 }
544 }
545 QueryForm::Select => solutions
546 .iter()
547 .map(|b| SparqlViolation {
548 value: b.get("value").cloned(),
549 path: b.get("path").cloned(),
550 message: b.get("message").cloned(),
551 bindings: b.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
552 })
553 .collect(),
554 }
555 }
556
557 fn run_fallback(&self, query: &Query, focus: &Term) -> Result<Vec<SparqlViolation>, String> {
560 let mut query = query.clone();
561 substitute_query(&mut query, &variable("this"), focus);
562 let prepared = self.evaluator().for_query(query);
563 let query_result = if let Some(frozen) = &self.frozen {
564 prepared
565 .on_queryable_dataset(frozen)
566 .execute()
567 .map_err(err)?
568 } else {
569 prepared.on_store(self.store()?).execute().map_err(err)?
570 };
571 match query_result {
572 QueryResults::Solutions(solutions) => {
573 let vars: Vec<String> = solutions
574 .variables()
575 .iter()
576 .map(|v| v.as_str().to_string())
577 .collect();
578 solutions
579 .map(|solution| {
580 let solution = solution.map_err(err)?;
581 let bindings = vars
582 .iter()
583 .filter_map(|name| {
584 solution
585 .get(name.as_str())
586 .map(|t| (name.clone(), t.clone()))
587 })
588 .collect();
589 Ok(SparqlViolation {
590 value: solution.get("value").cloned(),
591 path: solution.get("path").cloned(),
592 message: solution.get("message").cloned(),
593 bindings,
594 })
595 })
596 .collect()
597 }
598 QueryResults::Boolean(violates) => Ok(if violates {
599 vec![SparqlViolation {
600 value: None,
601 path: None,
602 message: None,
603 bindings: HashMap::new(),
604 }]
605 } else {
606 Vec::new()
607 }),
608 QueryResults::Graph(_) => {
609 Err("SPARQL constraint unexpectedly produced graph results".to_string())
610 }
611 }
612 }
613
614 pub fn prefetch_constraint(
622 &self,
623 constraint: &SparqlConstraint,
624 foci: &[Term],
625 ) -> Result<(), String> {
626 let compiled = self.compile_constraint(constraint)?;
627 if compiled.plan.is_some() || compiled.batched.borrow().is_some() {
628 return Ok(());
629 }
630 if foci.len() < 2 {
631 return Ok(());
632 }
633 let fp = profile::fingerprint(&constraint.query);
634 let start = web_time::Instant::now();
635 if let Some(batched) = self.run_fallback_batch(&compiled.query, foci)? {
636 *compiled.batched.borrow_mut() = Some(batched);
637 profile::record(
638 &fp,
639 start.elapsed().as_micros() as u64,
640 profile::ExecutorKind::Fallback { reason: None },
641 );
642 }
643 Ok(())
644 }
645
646 fn run_fallback_batch(
651 &self,
652 query: &Query,
653 foci: &[Term],
654 ) -> Result<Option<BatchedResults>, String> {
655 let Some((batched, covered)) = plan_batch_query(query, foci) else {
656 return Ok(None);
657 };
658 let covered: HashSet<Term> = covered.into_iter().collect();
659 let prepared = self.evaluator().for_query(batched);
660 let query_result = if let Some(frozen) = &self.frozen {
661 prepared
662 .on_queryable_dataset(frozen)
663 .execute()
664 .map_err(err)?
665 } else {
666 prepared.on_store(self.store()?).execute().map_err(err)?
667 };
668 let QueryResults::Solutions(solutions) = query_result else {
669 return Ok(None);
670 };
671 let vars: Vec<String> = solutions
672 .variables()
673 .iter()
674 .map(|v| v.as_str().to_string())
675 .collect();
676 let mut map: HashMap<Term, Vec<SparqlViolation>> = HashMap::new();
677 for solution in solutions {
678 let solution = solution.map_err(err)?;
679 let Some(this) = solution.get("this") else {
680 continue;
681 };
682 if !covered.contains(this) {
685 continue;
686 }
687 let bindings = vars
691 .iter()
692 .filter(|name| name.as_str() != "this")
693 .filter_map(|name| {
694 solution
695 .get(name.as_str())
696 .map(|t| (name.clone(), t.clone()))
697 })
698 .collect();
699 map.entry(this.clone()).or_default().push(SparqlViolation {
700 value: solution.get("value").cloned(),
701 path: solution.get("path").cloned(),
702 message: solution.get("message").cloned(),
703 bindings,
704 });
705 }
706 Ok(Some(BatchedResults { covered, map }))
707 }
708
709 pub fn construct_many(
711 &self,
712 query: &str,
713 foci: &[Term],
714 frozen: Option<&FrozenIndexedDataset>,
715 ) -> Result<Vec<Triple>, String> {
716 const BATCH_SIZE: usize = 2048;
717
718 if foci.is_empty() {
719 return Ok(Vec::new());
720 }
721
722 let fp = profile::fingerprint(query);
723 let start = web_time::Instant::now();
724 let mut triples = Vec::new();
725 let compiled = self.compile_construct(query)?;
726 let executor = if let (Some(plan), Some(frozen)) = (&compiled.plan, frozen) {
727 for chunk in foci.chunks(BATCH_SIZE) {
728 let result = native_exec::execute_ids(plan, frozen, chunk);
729 for (focus_index, solutions) in result.solutions.into_iter().enumerate() {
730 for bindings in solutions {
731 instantiate_template(
732 &compiled.template,
733 plan,
734 frozen,
735 &chunk[focus_index],
736 &bindings,
737 &mut triples,
738 );
739 }
740 }
741 }
742 profile::ExecutorKind::Native
743 } else {
744 const PROBE_BUDGET: std::time::Duration = std::time::Duration::from_secs(2);
772 let this_var = variable("this");
773 let parsed = self.parse(query)?;
774 let body_binds_this = matches!(
775 &parsed,
776 Query::Construct { pattern, .. } if this_required(pattern, &this_var)
777 );
778 let subject_is_focus = !compiled.template.is_empty()
779 && compiled
780 .template
781 .iter()
782 .all(|t| term_pattern_is(&t.subject, &this_var))
783 && body_binds_this;
784 drop(parsed);
785
786 let blank_foci = || foci.iter().filter(|f| matches!(f, Term::BlankNode(_)));
787
788 if subject_is_focus && self.batch_construct.borrow().contains(query) {
789 let foci_set: HashSet<Term> = foci.iter().cloned().collect();
792 self.construct_free_filtered(query, &foci_set, &mut triples)?;
793 } else if subject_is_focus {
794 let named: Vec<&Term> = foci
797 .iter()
798 .filter(|f| !matches!(f, Term::BlankNode(_)))
799 .collect();
800 let deadline = web_time::Instant::now() + PROBE_BUDGET;
801 let mut probed = Vec::new();
802 let mut bailed = false;
803 for (i, focus) in named.iter().enumerate() {
804 probed.extend(self.construct_one(query, focus)?);
805 if i + 1 < named.len() && web_time::Instant::now() >= deadline {
806 bailed = true;
807 break;
808 }
809 }
810 if bailed {
811 self.batch_construct.borrow_mut().insert(query.to_string());
814 let foci_set: HashSet<Term> = foci.iter().cloned().collect();
815 self.construct_free_filtered(query, &foci_set, &mut triples)?;
816 } else {
817 triples.append(&mut probed);
818 if blank_foci().next().is_some() {
823 let blank_set: HashSet<Term> = blank_foci().cloned().collect();
824 self.construct_free_filtered(query, &blank_set, &mut triples)?;
825 }
826 }
827 } else {
828 for focus in foci.iter().filter(|f| !matches!(f, Term::BlankNode(_))) {
832 triples.extend(self.construct_one(query, focus)?);
833 }
834 if blank_foci().next().is_some() {
835 let blank_set: HashSet<Term> = blank_foci().cloned().collect();
836 self.construct_free_filtered(query, &blank_set, &mut triples)?;
837 }
838 }
839
840 let store = self.store()?;
841 triples.retain(|triple| {
842 !store
843 .contains(QuadRef::new(
844 triple.subject.as_ref(),
845 triple.predicate.as_ref(),
846 triple.object.as_ref(),
847 GraphNameRef::DefaultGraph,
848 ))
849 .unwrap_or(false)
850 });
851 profile::ExecutorKind::Fallback { reason: None }
852 };
853
854 profile::record(&fp, start.elapsed().as_micros() as u64, executor);
855 Ok(triples)
856 }
857
858 pub fn construct_delta_foci(
862 &self,
863 query: &str,
864 delta: &[Triple],
865 frozen: Option<&FrozenIndexedDataset>,
866 ) -> Result<Option<HashSet<Term>>, String> {
867 let compiled = self.compile_construct(query)?;
868 let (Some(plan), Some(frozen)) = (&compiled.plan, frozen) else {
869 return Ok(None);
870 };
871 Ok(
872 native_exec::delta_focus_ids(plan, frozen, delta).map(|ids| {
873 ids.into_iter()
874 .filter_map(|id| frozen.externalize(id))
875 .collect()
876 }),
877 )
878 }
879
880 fn compile_construct(&self, query: &str) -> Result<Rc<CompiledConstruct>, String> {
881 if let Some(compiled) = self.constructs.borrow().get(query) {
882 return Ok(compiled.clone());
883 }
884 let parsed = self.parse(query)?;
885 let Query::Construct {
886 template,
887 dataset,
888 pattern,
889 base_iri,
890 } = parsed
891 else {
892 return Err("SPARQL rule did not contain a CONSTRUCT query".to_string());
893 };
894
895 let plan = if dataset.is_none() && !template.iter().any(triple_has_blank_node) {
896 let stats = self.frozen.as_ref().map(|f| f.plan_stats());
897 lower_query_with_stats(
898 &Query::Select {
899 dataset: None,
900 pattern,
901 base_iri,
902 },
903 stats.as_ref(),
904 )
905 .ok()
906 } else {
907 None
908 };
909 let compiled = Rc::new(CompiledConstruct { plan, template });
910 self.constructs
911 .borrow_mut()
912 .insert(query.to_string(), compiled.clone());
913 Ok(compiled)
914 }
915
916 fn construct_one(&self, query: &str, focus: &Term) -> Result<Vec<Triple>, String> {
917 let mut query = self.parse(query)?;
918 substitute_query(&mut query, &variable("this"), focus);
919 let prepared = self.evaluator().for_query(query);
920 match prepared.on_store(self.store()?).execute().map_err(err)? {
921 QueryResults::Graph(triples) => triples.map(|triple| triple.map_err(err)).collect(),
922 _ => Err("SPARQL rule did not produce CONSTRUCT graph results".to_string()),
923 }
924 }
925
926 fn construct_free_filtered(
931 &self,
932 query: &str,
933 foci_set: &HashSet<Term>,
934 out: &mut Vec<Triple>,
935 ) -> Result<(), String> {
936 match self
937 .evaluator()
938 .for_query(self.parse(query)?)
939 .on_store(self.store()?)
940 .execute()
941 .map_err(err)?
942 {
943 QueryResults::Graph(iter) => {
944 for triple in iter {
945 let triple = triple.map_err(err)?;
946 if foci_set.contains(&Term::from(triple.subject.clone())) {
947 out.push(triple);
948 }
949 }
950 Ok(())
951 }
952 _ => Err("SPARQL rule did not produce CONSTRUCT graph results".to_string()),
953 }
954 }
955
956 fn prepared(&self, query: &str) -> Result<PreparedSparqlQuery, String> {
957 if let Some(prepared) = self.prepared.borrow().get(query) {
958 return Ok(prepared.clone());
959 }
960 let prepared = self.evaluator().parse_query(query).map_err(err)?;
961 self.prepared
962 .borrow_mut()
963 .insert(query.to_string(), prepared.clone());
964 Ok(prepared)
965 }
966
967 fn parse(&self, query: &str) -> Result<Query, String> {
971 if let Some(parsed) = self.parsed.borrow().get(query) {
972 return Ok(parsed.clone());
973 }
974 let parsed = SparqlParser::new().parse_query(query).map_err(err)?;
975 self.parsed
976 .borrow_mut()
977 .insert(query.to_string(), parsed.clone());
978 Ok(parsed)
979 }
980
981 pub fn call_sparql_function(
984 &self,
985 raw_query: &str,
986 params: &[(String, Term)],
987 ) -> Result<Vec<Term>, String> {
988 let mut query = self.parse(raw_query)?;
989 for (name, value) in params {
990 let var = Variable::new(name).map_err(err)?;
991 substitute_query(&mut query, &var, value);
992 }
993 let prepared = SparqlEvaluator::new().for_query(query);
994 let result = if let Some(frozen) = &self.frozen {
995 prepared
996 .on_queryable_dataset(frozen)
997 .execute()
998 .map_err(err)?
999 } else {
1000 prepared.on_store(self.store()?).execute().map_err(err)?
1001 };
1002 match result {
1003 QueryResults::Solutions(solutions) => {
1004 let mut out = Vec::new();
1005 for sol in solutions {
1006 if let Some(t) = sol.map_err(err)?.get("result") {
1007 out.push(t.clone());
1008 }
1009 }
1010 Ok(out)
1011 }
1012 QueryResults::Boolean(b) => Ok(if b {
1013 vec![Term::Literal(Literal::from(true))]
1014 } else {
1015 vec![]
1016 }),
1017 QueryResults::Graph(_) => Err("sh:SPARQLFunction produced graph results".to_string()),
1018 }
1019 }
1020
1021 pub(crate) fn eval_ask(
1026 &self,
1027 raw_query: &str,
1028 path: Option<&Path>,
1029 bindings: &[(String, Term)],
1030 ) -> Result<bool, String> {
1031 match self.run_substituted(raw_query, path, bindings)? {
1032 QueryResults::Boolean(b) => Ok(b),
1033 _ => Err("sh:SPARQLAskValidator query is not an ASK".to_string()),
1034 }
1035 }
1036
1037 pub(crate) fn eval_select(
1042 &self,
1043 raw_query: &str,
1044 path: Option<&Path>,
1045 bindings: &[(String, Term)],
1046 ) -> Result<Vec<HashMap<String, Term>>, String> {
1047 match self.run_substituted(raw_query, path, bindings)? {
1048 QueryResults::Solutions(solutions) => {
1049 let vars: Vec<String> = solutions
1050 .variables()
1051 .iter()
1052 .map(|v| v.as_str().to_string())
1053 .collect();
1054 solutions
1055 .map(|solution| {
1056 let solution = solution.map_err(err)?;
1057 Ok(vars
1058 .iter()
1059 .filter_map(|name| {
1060 solution
1061 .get(name.as_str())
1062 .map(|t| (name.clone(), t.clone()))
1063 })
1064 .collect())
1065 })
1066 .collect()
1067 }
1068 _ => Err("sh:SPARQLSelectValidator query is not a SELECT".to_string()),
1069 }
1070 }
1071
1072 fn run_substituted(
1075 &self,
1076 raw_query: &str,
1077 path: Option<&Path>,
1078 bindings: &[(String, Term)],
1079 ) -> Result<QueryResults<'_>, String> {
1080 let mut query = self.parse(raw_query)?;
1081 if let Some(path) = path {
1082 query = apply_path_prebinding(query, path)?;
1083 }
1084 for (name, value) in bindings {
1085 let var = Variable::new(name).map_err(err)?;
1086 substitute_query(&mut query, &var, value);
1087 }
1088 let prepared = self.evaluator().for_query(query);
1089 if let Some(frozen) = &self.frozen {
1090 prepared.on_queryable_dataset(frozen).execute().map_err(err)
1091 } else {
1092 prepared.on_store(self.store()?).execute().map_err(err)
1093 }
1094 }
1095
1096 fn store(&self) -> Result<&Store, String> {
1097 self.store
1098 .as_ref()
1099 .ok_or_else(|| "mutable SPARQL store is unavailable during validation".to_string())
1100 }
1101
1102 #[cfg(test)]
1103 fn has_store(&self) -> bool {
1104 self.store.is_some()
1105 }
1106}
1107
1108fn triple_has_blank_node(triple: &TriplePattern) -> bool {
1109 matches!(triple.subject, TermPattern::BlankNode(_))
1110 || matches!(triple.object, TermPattern::BlankNode(_))
1111}
1112
1113fn instantiate_template(
1114 template: &[TriplePattern],
1115 plan: &NativeQueryPlan,
1116 frozen: &FrozenIndexedDataset,
1117 focus: &Term,
1118 bindings: &native_exec::NativeIdBindings,
1119 out: &mut Vec<Triple>,
1120) {
1121 for triple in template {
1122 let Some(subject_id) = resolve_template_id(&triple.subject, plan, frozen, focus, bindings)
1123 else {
1124 continue;
1125 };
1126 let Some(predicate_id) =
1127 resolve_template_predicate_id(&triple.predicate, plan, frozen, focus, bindings)
1128 else {
1129 continue;
1130 };
1131 let Some(object_id) = resolve_template_id(&triple.object, plan, frozen, focus, bindings)
1132 else {
1133 continue;
1134 };
1135 if frozen.contains_ids(subject_id, predicate_id, object_id) {
1136 continue;
1137 }
1138
1139 let Some(subject) = frozen.externalize(subject_id).and_then(|term| match term {
1140 Term::NamedNode(node) => Some(NamedOrBlankNode::NamedNode(node)),
1141 Term::BlankNode(node) => Some(NamedOrBlankNode::BlankNode(node)),
1142 Term::Literal(_) => None,
1143 }) else {
1144 continue;
1145 };
1146 let Some(Term::NamedNode(predicate)) = frozen.externalize(predicate_id) else {
1147 continue;
1148 };
1149 let Some(object) = frozen.externalize(object_id) else {
1150 continue;
1151 };
1152 out.push(Triple::new(subject, predicate, object));
1153 }
1154}
1155
1156fn resolve_template_id(
1157 pattern: &TermPattern,
1158 plan: &NativeQueryPlan,
1159 frozen: &FrozenIndexedDataset,
1160 focus: &Term,
1161 bindings: &native_exec::NativeIdBindings,
1162) -> Option<TermId> {
1163 match pattern {
1164 TermPattern::NamedNode(node) => Some(frozen.intern(&Term::NamedNode(node.clone()))),
1165 TermPattern::BlankNode(node) => Some(frozen.intern(&Term::BlankNode(node.clone()))),
1166 TermPattern::Literal(literal) => Some(frozen.intern(&Term::Literal(literal.clone()))),
1167 TermPattern::Variable(variable) if variable.as_str() == "this" => {
1168 Some(frozen.intern(focus))
1169 }
1170 TermPattern::Variable(variable) => {
1171 let id = plan.var_id(variable.as_str())?;
1172 bindings.get(&id).copied()
1173 }
1174 #[allow(unreachable_patterns)]
1175 _ => None,
1176 }
1177}
1178
1179fn resolve_template_predicate_id(
1180 pattern: &NamedNodePattern,
1181 plan: &NativeQueryPlan,
1182 frozen: &FrozenIndexedDataset,
1183 focus: &Term,
1184 bindings: &native_exec::NativeIdBindings,
1185) -> Option<TermId> {
1186 match pattern {
1187 NamedNodePattern::NamedNode(node) => Some(frozen.intern(&Term::NamedNode(node.clone()))),
1188 NamedNodePattern::Variable(variable) => {
1189 if variable.as_str() == "this" {
1190 Some(frozen.intern(focus))
1191 } else {
1192 let id = plan.var_id(variable.as_str())?;
1193 bindings.get(&id).copied()
1194 }
1195 }
1196 }
1197}
1198
1199fn path_to_property_path(path: &Path) -> Option<PropertyPathExpression> {
1203 match path {
1204 Path::Id => None,
1205 Path::Pred(n) => Some(PropertyPathExpression::NamedNode(n.clone())),
1206 Path::Inverse(inner) => {
1207 path_to_property_path(inner).map(|p| PropertyPathExpression::Reverse(Box::new(p)))
1208 }
1209 Path::Seq(parts) => {
1210 let sparql: Vec<_> = parts
1211 .iter()
1212 .map(path_to_property_path)
1213 .collect::<Option<Vec<_>>>()?;
1214 sparql
1215 .into_iter()
1216 .reduce(|a, b| PropertyPathExpression::Sequence(Box::new(a), Box::new(b)))
1217 }
1218 Path::Alt(parts) => {
1219 let has_id = parts.iter().any(|p| matches!(p, Path::Id));
1222 let sparql: Vec<_> = parts
1223 .iter()
1224 .filter(|p| !matches!(p, Path::Id))
1225 .map(path_to_property_path)
1226 .collect::<Option<Vec<_>>>()?;
1227 if sparql.is_empty() {
1228 return None;
1229 }
1230 let base = sparql
1231 .into_iter()
1232 .reduce(|a, b| PropertyPathExpression::Alternative(Box::new(a), Box::new(b)))?;
1233 if has_id {
1234 Some(PropertyPathExpression::ZeroOrOne(Box::new(base)))
1235 } else {
1236 Some(base)
1237 }
1238 }
1239 Path::Star(inner) => {
1240 path_to_property_path(inner).map(|p| PropertyPathExpression::ZeroOrMore(Box::new(p)))
1241 }
1242 }
1243}
1244
1245fn apply_path_prebinding(mut query: Query, path: &Path) -> Result<Query, String> {
1252 match path {
1253 Path::Pred(predicate) => {
1254 substitute_query(
1255 &mut query,
1256 &variable("PATH"),
1257 &Term::NamedNode(predicate.clone()),
1258 );
1259 Ok(query)
1260 }
1261 complex => {
1262 let sparql_path = path_to_property_path(complex).ok_or_else(|| {
1263 format!("SHACL path cannot be expressed as a SPARQL property path: {complex:?}")
1264 })?;
1265 Ok(rewrite_path_query(query, &sparql_path))
1266 }
1267 }
1268}
1269
1270fn rewrite_path_query(query: Query, path: &PropertyPathExpression) -> Query {
1274 match query {
1275 Query::Select {
1276 dataset,
1277 pattern,
1278 base_iri,
1279 } => Query::Select {
1280 dataset,
1281 pattern: rewrite_path_pattern(pattern, path),
1282 base_iri,
1283 },
1284 Query::Ask {
1285 dataset,
1286 pattern,
1287 base_iri,
1288 } => Query::Ask {
1289 dataset,
1290 pattern: rewrite_path_pattern(pattern, path),
1291 base_iri,
1292 },
1293 other => other,
1294 }
1295}
1296
1297fn rewrite_path_pattern(pattern: GraphPattern, path: &PropertyPathExpression) -> GraphPattern {
1298 match pattern {
1299 GraphPattern::Bgp { patterns } => {
1300 let mut result = GraphPattern::Bgp { patterns: vec![] };
1301 let mut remaining = Vec::new();
1302 for triple in patterns {
1303 if matches!(&triple.predicate, NamedNodePattern::Variable(v) if v.as_str() == "PATH")
1304 {
1305 let path_gp = GraphPattern::Path {
1306 subject: triple.subject,
1307 path: path.clone(),
1308 object: triple.object,
1309 };
1310 result = GraphPattern::Join {
1311 left: Box::new(result),
1312 right: Box::new(path_gp),
1313 };
1314 } else {
1315 remaining.push(triple);
1316 }
1317 }
1318 if remaining.is_empty() {
1319 result
1320 } else {
1321 let bgp = GraphPattern::Bgp {
1322 patterns: remaining,
1323 };
1324 GraphPattern::Join {
1325 left: Box::new(bgp),
1326 right: Box::new(result),
1327 }
1328 }
1329 }
1330 GraphPattern::Path { .. } => pattern,
1332 GraphPattern::Join { left, right } => GraphPattern::Join {
1333 left: Box::new(rewrite_path_pattern(*left, path)),
1334 right: Box::new(rewrite_path_pattern(*right, path)),
1335 },
1336 GraphPattern::Union { left, right } => GraphPattern::Union {
1337 left: Box::new(rewrite_path_pattern(*left, path)),
1338 right: Box::new(rewrite_path_pattern(*right, path)),
1339 },
1340 GraphPattern::Minus { left, right } => GraphPattern::Minus {
1341 left: Box::new(rewrite_path_pattern(*left, path)),
1342 right: Box::new(rewrite_path_pattern(*right, path)),
1343 },
1344 GraphPattern::Lateral { left, right } => GraphPattern::Lateral {
1345 left: Box::new(rewrite_path_pattern(*left, path)),
1346 right: Box::new(rewrite_path_pattern(*right, path)),
1347 },
1348 GraphPattern::LeftJoin {
1349 left,
1350 right,
1351 expression,
1352 } => GraphPattern::LeftJoin {
1353 left: Box::new(rewrite_path_pattern(*left, path)),
1354 right: Box::new(rewrite_path_pattern(*right, path)),
1355 expression,
1356 },
1357 GraphPattern::Filter { expr, inner } => GraphPattern::Filter {
1358 expr,
1359 inner: Box::new(rewrite_path_pattern(*inner, path)),
1360 },
1361 GraphPattern::Graph { name, inner } => GraphPattern::Graph {
1362 name,
1363 inner: Box::new(rewrite_path_pattern(*inner, path)),
1364 },
1365 GraphPattern::Extend {
1366 inner,
1367 variable,
1368 expression,
1369 } => GraphPattern::Extend {
1370 inner: Box::new(rewrite_path_pattern(*inner, path)),
1371 variable,
1372 expression,
1373 },
1374 GraphPattern::OrderBy { inner, expression } => GraphPattern::OrderBy {
1375 inner: Box::new(rewrite_path_pattern(*inner, path)),
1376 expression,
1377 },
1378 GraphPattern::Project { inner, variables } => GraphPattern::Project {
1379 inner: Box::new(rewrite_path_pattern(*inner, path)),
1380 variables,
1381 },
1382 GraphPattern::Distinct { inner } => GraphPattern::Distinct {
1383 inner: Box::new(rewrite_path_pattern(*inner, path)),
1384 },
1385 GraphPattern::Reduced { inner } => GraphPattern::Reduced {
1386 inner: Box::new(rewrite_path_pattern(*inner, path)),
1387 },
1388 GraphPattern::Slice {
1389 inner,
1390 start,
1391 length,
1392 } => GraphPattern::Slice {
1393 inner: Box::new(rewrite_path_pattern(*inner, path)),
1394 start,
1395 length,
1396 },
1397 GraphPattern::Group {
1398 inner,
1399 variables,
1400 aggregates,
1401 } => GraphPattern::Group {
1402 inner: Box::new(rewrite_path_pattern(*inner, path)),
1403 variables,
1404 aggregates,
1405 },
1406 GraphPattern::Service {
1407 name,
1408 inner,
1409 silent,
1410 } => GraphPattern::Service {
1411 name,
1412 inner: Box::new(rewrite_path_pattern(*inner, path)),
1413 silent,
1414 },
1415 GraphPattern::Values { .. } => pattern,
1416 }
1417}
1418
1419fn plan_batch_query(query: &Query, foci: &[Term]) -> Option<(Query, Vec<Term>)> {
1441 let this = Variable::new("this").ok()?;
1442 let (pattern, is_ask) = match query {
1443 Query::Select { pattern, .. } => (pattern, false),
1444 Query::Ask { pattern, .. } => (pattern, true),
1445 _ => return None,
1446 };
1447 if pattern_has_group_or_slice(pattern) || !this_required(pattern, &this) {
1448 return None;
1449 }
1450
1451 let covered: Vec<Term> = foci
1452 .iter()
1453 .filter(|f| matches!(f, Term::NamedNode(_)))
1454 .cloned()
1455 .collect();
1456 if covered.len() < 2 {
1457 return None;
1458 }
1459
1460 let (dataset, base_iri) = match query {
1461 Query::Select {
1462 dataset, base_iri, ..
1463 }
1464 | Query::Ask {
1465 dataset, base_iri, ..
1466 } => (dataset.clone(), base_iri.clone()),
1467 _ => unreachable!(),
1468 };
1469
1470 let rewritten = if is_ask {
1471 Query::Select {
1475 dataset,
1476 pattern: GraphPattern::Project {
1477 inner: Box::new(GraphPattern::Distinct {
1478 inner: Box::new(pattern.clone()),
1479 }),
1480 variables: vec![this],
1481 },
1482 base_iri,
1483 }
1484 } else {
1485 Query::Select {
1486 dataset,
1487 pattern: ensure_projected(pattern.clone(), &this)?,
1488 base_iri,
1489 }
1490 };
1491 Some((rewritten, covered))
1492}
1493
1494fn this_required(p: &GraphPattern, var: &Variable) -> bool {
1501 match p {
1502 GraphPattern::Bgp { patterns } => patterns
1503 .iter()
1504 .any(|t| term_pattern_is(&t.subject, var) || term_pattern_is(&t.object, var)),
1505 GraphPattern::Path {
1506 subject, object, ..
1507 } => term_pattern_is(subject, var) || term_pattern_is(object, var),
1508 GraphPattern::Join { left, right } | GraphPattern::Lateral { left, right } => {
1509 this_required(left, var) || this_required(right, var)
1510 }
1511 GraphPattern::Union { left, right } => {
1512 this_required(left, var) && this_required(right, var)
1513 }
1514 GraphPattern::LeftJoin { left, .. } | GraphPattern::Minus { left, .. } => {
1516 this_required(left, var)
1517 }
1518 GraphPattern::Filter { inner, .. }
1519 | GraphPattern::Graph { inner, .. }
1520 | GraphPattern::Extend { inner, .. }
1521 | GraphPattern::OrderBy { inner, .. }
1522 | GraphPattern::Project { inner, .. }
1523 | GraphPattern::Distinct { inner }
1524 | GraphPattern::Reduced { inner }
1525 | GraphPattern::Slice { inner, .. }
1526 | GraphPattern::Group { inner, .. }
1527 | GraphPattern::Service { inner, .. } => this_required(inner, var),
1528 GraphPattern::Values {
1529 variables,
1530 bindings,
1531 } => {
1532 variables.iter().position(|v| v == var).is_some_and(|col| {
1534 bindings
1535 .iter()
1536 .all(|row| row.get(col).is_some_and(Option::is_some))
1537 })
1538 }
1539 }
1540}
1541
1542fn ensure_projected(pattern: GraphPattern, this: &Variable) -> Option<GraphPattern> {
1548 match pattern {
1549 GraphPattern::Project {
1550 inner,
1551 mut variables,
1552 } => {
1553 if !variables.iter().any(|v| v == this) {
1554 variables.push(this.clone());
1555 }
1556 Some(GraphPattern::Project { inner, variables })
1557 }
1558 GraphPattern::Distinct { inner } => Some(GraphPattern::Distinct {
1559 inner: Box::new(ensure_projected(*inner, this)?),
1560 }),
1561 GraphPattern::Reduced { inner } => Some(GraphPattern::Reduced {
1562 inner: Box::new(ensure_projected(*inner, this)?),
1563 }),
1564 GraphPattern::OrderBy { inner, expression } => Some(GraphPattern::OrderBy {
1565 inner: Box::new(ensure_projected(*inner, this)?),
1566 expression,
1567 }),
1568 _ => None,
1569 }
1570}
1571
1572fn pattern_has_group_or_slice(p: &GraphPattern) -> bool {
1575 match p {
1576 GraphPattern::Group { .. } | GraphPattern::Slice { .. } => true,
1577 GraphPattern::Bgp { .. } | GraphPattern::Path { .. } | GraphPattern::Values { .. } => false,
1578 GraphPattern::Join { left, right }
1579 | GraphPattern::Union { left, right }
1580 | GraphPattern::Minus { left, right }
1581 | GraphPattern::Lateral { left, right }
1582 | GraphPattern::LeftJoin { left, right, .. } => {
1583 pattern_has_group_or_slice(left) || pattern_has_group_or_slice(right)
1584 }
1585 GraphPattern::Filter { inner, .. }
1586 | GraphPattern::Graph { inner, .. }
1587 | GraphPattern::Extend { inner, .. }
1588 | GraphPattern::OrderBy { inner, .. }
1589 | GraphPattern::Project { inner, .. }
1590 | GraphPattern::Distinct { inner }
1591 | GraphPattern::Reduced { inner }
1592 | GraphPattern::Service { inner, .. } => pattern_has_group_or_slice(inner),
1593 }
1594}
1595
1596fn term_pattern_is(t: &TermPattern, var: &Variable) -> bool {
1597 matches!(t, TermPattern::Variable(v) if v == var)
1598}
1599
1600pub(crate) fn query_reads_graph(query: &str) -> bool {
1608 let Ok(parsed) = SparqlParser::new().parse_query(query) else {
1609 return true;
1610 };
1611 fn walk(pattern: &GraphPattern) -> bool {
1612 use GraphPattern::*;
1613 match pattern {
1614 Bgp { patterns } => !patterns.is_empty(),
1615 Path { .. } | Graph { .. } | Service { .. } => true,
1616 Join { left, right }
1617 | Union { left, right }
1618 | Minus { left, right }
1619 | Lateral { left, right } => walk(left) || walk(right),
1620 LeftJoin { left, right, .. } => walk(left) || walk(right),
1621 Filter { inner, .. }
1622 | Extend { inner, .. }
1623 | Project { inner, .. }
1624 | Distinct { inner }
1625 | Reduced { inner }
1626 | Slice { inner, .. }
1627 | OrderBy { inner, .. }
1628 | Group { inner, .. } => walk(inner),
1629 _ => false,
1630 }
1631 }
1632 match &parsed {
1633 Query::Select { pattern, .. } | Query::Ask { pattern, .. } => walk(pattern),
1634 _ => true,
1635 }
1636}
1637
1638fn eval_sparql_function(query: &str, params: &[String], args: &[Term]) -> Option<Term> {
1644 let mut q = SparqlParser::new().parse_query(query).ok()?;
1645 for (name, value) in params.iter().zip(args) {
1646 if let Ok(var) = Variable::new(name) {
1647 substitute_query(&mut q, &var, value);
1648 }
1649 }
1650 let store = Store::new().ok()?;
1651 match SparqlEvaluator::new()
1652 .for_query(q)
1653 .on_store(&store)
1654 .execute()
1655 .ok()?
1656 {
1657 QueryResults::Solutions(mut solutions) => solutions.next()?.ok()?.get("result").cloned(),
1658 QueryResults::Boolean(b) => Some(Term::Literal(Literal::from(b))),
1659 QueryResults::Graph(_) => None,
1660 }
1661}
1662
1663fn rename_variable_in_query(query: &mut Query, from: &Variable, to: &Variable) {
1668 match query {
1669 Query::Select { pattern, .. }
1670 | Query::Describe { pattern, .. }
1671 | Query::Ask { pattern, .. } => rename_var_pattern(pattern, from, to),
1672 Query::Construct {
1673 template, pattern, ..
1674 } => {
1675 for triple in template {
1676 rename_var_triple(triple, from, to);
1677 }
1678 rename_var_pattern(pattern, from, to);
1679 }
1680 }
1681}
1682
1683fn rename_var_pattern(pattern: &mut GraphPattern, from: &Variable, to: &Variable) {
1684 match pattern {
1685 GraphPattern::Bgp { patterns } => {
1686 for triple in patterns {
1687 rename_var_triple(triple, from, to);
1688 }
1689 }
1690 GraphPattern::Path {
1691 subject, object, ..
1692 } => {
1693 rename_var_term_pattern(subject, from, to);
1694 rename_var_term_pattern(object, from, to);
1695 }
1696 GraphPattern::Join { left, right }
1697 | GraphPattern::Union { left, right }
1698 | GraphPattern::Minus { left, right }
1699 | GraphPattern::Lateral { left, right } => {
1700 rename_var_pattern(left, from, to);
1701 rename_var_pattern(right, from, to);
1702 }
1703 GraphPattern::LeftJoin {
1704 left,
1705 right,
1706 expression,
1707 } => {
1708 rename_var_pattern(left, from, to);
1709 rename_var_pattern(right, from, to);
1710 if let Some(expr) = expression {
1711 rename_var_expr(expr, from, to);
1712 }
1713 }
1714 GraphPattern::Filter { expr, inner } => {
1715 rename_var_expr(expr, from, to);
1716 rename_var_pattern(inner, from, to);
1717 }
1718 GraphPattern::Graph { name, inner } => {
1719 if let NamedNodePattern::Variable(v) = name
1720 && v == from
1721 {
1722 *name = NamedNodePattern::Variable(to.clone());
1723 }
1724 rename_var_pattern(inner, from, to);
1725 }
1726 GraphPattern::Extend {
1727 inner,
1728 expression,
1729 variable,
1730 } => {
1731 rename_var_pattern(inner, from, to);
1732 rename_var_expr(expression, from, to);
1733 if variable == from {
1734 *variable = to.clone();
1735 }
1736 }
1737 GraphPattern::OrderBy { inner, expression } => {
1738 rename_var_pattern(inner, from, to);
1739 for order in expression {
1740 match order {
1741 OrderExpression::Asc(e) | OrderExpression::Desc(e) => {
1742 rename_var_expr(e, from, to);
1743 }
1744 }
1745 }
1746 }
1747 GraphPattern::Project { inner, variables } => {
1748 rename_var_pattern(inner, from, to);
1749 for v in variables {
1750 if v == from {
1751 *v = to.clone();
1752 }
1753 }
1754 }
1755 GraphPattern::Distinct { inner }
1756 | GraphPattern::Reduced { inner }
1757 | GraphPattern::Slice { inner, .. } => rename_var_pattern(inner, from, to),
1758 GraphPattern::Group {
1759 inner,
1760 variables,
1761 aggregates,
1762 } => {
1763 rename_var_pattern(inner, from, to);
1764 for v in variables {
1765 if v == from {
1766 *v = to.clone();
1767 }
1768 }
1769 for (v, agg) in aggregates {
1770 if v == from {
1771 *v = to.clone();
1772 }
1773 if let AggregateExpression::FunctionCall { expr, .. } = agg {
1774 rename_var_expr(expr, from, to);
1775 }
1776 }
1777 }
1778 GraphPattern::Service { name, inner, .. } => {
1779 if let NamedNodePattern::Variable(v) = name
1780 && v == from
1781 {
1782 *name = NamedNodePattern::Variable(to.clone());
1783 }
1784 rename_var_pattern(inner, from, to);
1785 }
1786 GraphPattern::Values {
1787 variables,
1788 bindings,
1789 } => {
1790 for v in variables {
1791 if v == from {
1792 *v = to.clone();
1793 }
1794 }
1795 let _ = bindings; }
1797 }
1798}
1799
1800fn rename_var_triple(triple: &mut TriplePattern, from: &Variable, to: &Variable) {
1801 rename_var_term_pattern(&mut triple.subject, from, to);
1802 if let NamedNodePattern::Variable(v) = &mut triple.predicate
1803 && v == from
1804 {
1805 triple.predicate = NamedNodePattern::Variable(to.clone());
1806 }
1807 rename_var_term_pattern(&mut triple.object, from, to);
1808}
1809
1810fn rename_var_term_pattern(pattern: &mut TermPattern, from: &Variable, to: &Variable) {
1811 if let TermPattern::Variable(v) = pattern
1812 && v == from
1813 {
1814 *pattern = TermPattern::Variable(to.clone());
1815 }
1816}
1817
1818fn rename_var_expr(expr: &mut Expression, from: &Variable, to: &Variable) {
1819 match expr {
1820 Expression::Variable(v) if v == from => *v = to.clone(),
1821 Expression::Bound(v) if v == from => *v = to.clone(),
1822 Expression::Variable(_)
1823 | Expression::Bound(_)
1824 | Expression::NamedNode(_)
1825 | Expression::Literal(_) => {}
1826 Expression::Or(a, b)
1827 | Expression::And(a, b)
1828 | Expression::Equal(a, b)
1829 | Expression::SameTerm(a, b)
1830 | Expression::Greater(a, b)
1831 | Expression::GreaterOrEqual(a, b)
1832 | Expression::Less(a, b)
1833 | Expression::LessOrEqual(a, b)
1834 | Expression::Add(a, b)
1835 | Expression::Subtract(a, b)
1836 | Expression::Multiply(a, b)
1837 | Expression::Divide(a, b) => {
1838 rename_var_expr(a, from, to);
1839 rename_var_expr(b, from, to);
1840 }
1841 Expression::In(a, list) => {
1842 rename_var_expr(a, from, to);
1843 for e in list {
1844 rename_var_expr(e, from, to);
1845 }
1846 }
1847 Expression::UnaryPlus(a) | Expression::UnaryMinus(a) | Expression::Not(a) => {
1848 rename_var_expr(a, from, to);
1849 }
1850 Expression::Exists(pattern) => rename_var_pattern(pattern, from, to),
1851 Expression::If(a, b, c) => {
1852 rename_var_expr(a, from, to);
1853 rename_var_expr(b, from, to);
1854 rename_var_expr(c, from, to);
1855 }
1856 Expression::Coalesce(list) | Expression::FunctionCall(_, list) => {
1857 for e in list {
1858 rename_var_expr(e, from, to);
1859 }
1860 }
1861 }
1862}
1863
1864fn substitute_query(query: &mut Query, var: &Variable, value: &Term) {
1865 match query {
1866 Query::Select { pattern, .. }
1867 | Query::Describe { pattern, .. }
1868 | Query::Ask { pattern, .. } => substitute_pattern(pattern, var, value),
1869 Query::Construct {
1870 template, pattern, ..
1871 } => {
1872 for triple in template {
1873 substitute_triple(triple, var, value);
1874 }
1875 substitute_pattern(pattern, var, value);
1876 }
1877 }
1878}
1879
1880fn substitute_pattern(pattern: &mut GraphPattern, var: &Variable, value: &Term) {
1881 match pattern {
1882 GraphPattern::Bgp { patterns } => {
1883 for triple in patterns {
1884 substitute_triple(triple, var, value);
1885 }
1886 }
1887 GraphPattern::Path {
1888 subject, object, ..
1889 } => {
1890 substitute_term_pattern(subject, var, value);
1891 substitute_term_pattern(object, var, value);
1892 }
1893 GraphPattern::Join { left, right }
1894 | GraphPattern::Union { left, right }
1895 | GraphPattern::Minus { left, right }
1896 | GraphPattern::Lateral { left, right } => {
1897 substitute_pattern(left, var, value);
1898 substitute_pattern(right, var, value);
1899 }
1900 GraphPattern::LeftJoin {
1901 left,
1902 right,
1903 expression,
1904 } => {
1905 substitute_pattern(left, var, value);
1906 substitute_pattern(right, var, value);
1907 if let Some(expression) = expression {
1908 substitute_expr(expression, var, value);
1909 }
1910 }
1911 GraphPattern::Filter { expr, inner } => {
1912 substitute_expr(expr, var, value);
1913 substitute_pattern(inner, var, value);
1914 }
1915 GraphPattern::Graph { name, inner } => {
1916 substitute_named_node_pattern(name, var, value);
1917 substitute_pattern(inner, var, value);
1918 }
1919 GraphPattern::Extend {
1920 inner, expression, ..
1921 } => {
1922 substitute_pattern(inner, var, value);
1925 substitute_expr(expression, var, value);
1926 }
1927 GraphPattern::OrderBy { inner, expression } => {
1928 substitute_pattern(inner, var, value);
1929 for order in expression {
1930 match order {
1931 OrderExpression::Asc(e) | OrderExpression::Desc(e) => {
1932 substitute_expr(e, var, value)
1933 }
1934 }
1935 }
1936 }
1937 GraphPattern::Project { inner, .. }
1938 | GraphPattern::Distinct { inner }
1939 | GraphPattern::Reduced { inner }
1940 | GraphPattern::Slice { inner, .. } => substitute_pattern(inner, var, value),
1941 GraphPattern::Group {
1942 inner, aggregates, ..
1943 } => {
1944 substitute_pattern(inner, var, value);
1945 for (_, aggregate) in aggregates {
1946 if let AggregateExpression::FunctionCall { expr, .. } = aggregate {
1947 substitute_expr(expr, var, value);
1948 }
1949 }
1950 }
1951 GraphPattern::Service { name, inner, .. } => {
1952 substitute_named_node_pattern(name, var, value);
1953 substitute_pattern(inner, var, value);
1954 }
1955 GraphPattern::Values { .. } => {}
1956 }
1957}
1958
1959fn substitute_triple(triple: &mut TriplePattern, var: &Variable, value: &Term) {
1960 substitute_term_pattern(&mut triple.subject, var, value);
1961 substitute_named_node_pattern(&mut triple.predicate, var, value);
1962 substitute_term_pattern(&mut triple.object, var, value);
1963}
1964
1965fn substitute_term_pattern(pattern: &mut TermPattern, var: &Variable, value: &Term) {
1966 if matches!(pattern, TermPattern::Variable(v) if v == var)
1967 && let Some(replacement) = term_to_term_pattern(value)
1968 {
1969 *pattern = replacement;
1970 }
1971}
1972
1973fn substitute_named_node_pattern(pattern: &mut NamedNodePattern, var: &Variable, value: &Term) {
1974 if matches!(pattern, NamedNodePattern::Variable(v) if v == var)
1975 && let Term::NamedNode(node) = value
1976 {
1977 *pattern = NamedNodePattern::NamedNode(node.clone());
1978 }
1979}
1980
1981fn substitute_expr(expr: &mut Expression, var: &Variable, value: &Term) {
1982 match expr {
1983 Expression::Variable(v) if v == var => {
1984 if let Some(replacement) = term_to_expr(value) {
1985 *expr = replacement;
1986 }
1987 }
1988 Expression::Bound(v) if v == var => *expr = Expression::Literal(Literal::from(true)),
1990 Expression::Variable(_)
1991 | Expression::Bound(_)
1992 | Expression::NamedNode(_)
1993 | Expression::Literal(_) => {}
1994 Expression::Or(a, b)
1995 | Expression::And(a, b)
1996 | Expression::Equal(a, b)
1997 | Expression::SameTerm(a, b)
1998 | Expression::Greater(a, b)
1999 | Expression::GreaterOrEqual(a, b)
2000 | Expression::Less(a, b)
2001 | Expression::LessOrEqual(a, b)
2002 | Expression::Add(a, b)
2003 | Expression::Subtract(a, b)
2004 | Expression::Multiply(a, b)
2005 | Expression::Divide(a, b) => {
2006 substitute_expr(a, var, value);
2007 substitute_expr(b, var, value);
2008 }
2009 Expression::In(a, list) => {
2010 substitute_expr(a, var, value);
2011 for e in list {
2012 substitute_expr(e, var, value);
2013 }
2014 }
2015 Expression::UnaryPlus(a) | Expression::UnaryMinus(a) | Expression::Not(a) => {
2016 substitute_expr(a, var, value)
2017 }
2018 Expression::Exists(pattern) => substitute_pattern(pattern, var, value),
2019 Expression::If(a, b, c) => {
2020 substitute_expr(a, var, value);
2021 substitute_expr(b, var, value);
2022 substitute_expr(c, var, value);
2023 }
2024 Expression::Coalesce(list) | Expression::FunctionCall(_, list) => {
2025 for e in list {
2026 substitute_expr(e, var, value);
2027 }
2028 }
2029 }
2030}
2031
2032fn term_to_term_pattern(value: &Term) -> Option<TermPattern> {
2033 match value {
2034 Term::NamedNode(n) => Some(TermPattern::NamedNode(n.clone())),
2035 Term::BlankNode(b) => Some(TermPattern::BlankNode(b.clone())),
2036 Term::Literal(l) => Some(TermPattern::Literal(l.clone())),
2037 #[allow(unreachable_patterns)]
2039 _ => None,
2040 }
2041}
2042
2043fn term_to_expr(value: &Term) -> Option<Expression> {
2044 match value {
2045 Term::NamedNode(n) => Some(Expression::NamedNode(n.clone())),
2046 Term::Literal(l) => Some(Expression::Literal(l.clone())),
2047 _ => None,
2049 }
2050}
2051
2052fn variable(name: &str) -> Variable {
2053 Variable::new(name).expect("static SPARQL variable name")
2054}
2055
2056fn err(error: impl std::fmt::Display) -> String {
2057 error.to_string()
2058}
2059
2060fn path_key(path: &Path) -> String {
2062 shifty_algebra::render::path_to_string(path)
2063}
2064
2065#[cfg(debug_assertions)]
2068fn assert_violations_match(
2069 query: &str,
2070 focus: &Term,
2071 native: &[SparqlViolation],
2072 reference: &[SparqlViolation],
2073) {
2074 fn canonical(violations: &[SparqlViolation]) -> Vec<(String, String)> {
2075 let mut keyed: Vec<(String, String)> = violations
2076 .iter()
2077 .map(|v| {
2078 (
2079 v.value.as_ref().map(Term::to_string).unwrap_or_default(),
2080 v.path.as_ref().map(Term::to_string).unwrap_or_default(),
2081 )
2082 })
2083 .collect();
2084 keyed.sort();
2085 keyed
2086 }
2087 let native = canonical(native);
2088 let reference = canonical(reference);
2089 assert_eq!(
2090 native, reference,
2091 "native vs Spareval disagreement for focus {focus}\n query: {query}\n native: {native:?}\n spareval: {reference:?}",
2092 );
2093}
2094
2095#[cfg(test)]
2096mod storage_tests {
2097 use super::*;
2098
2099 #[test]
2100 fn validation_executor_does_not_allocate_mutable_store() {
2101 let executor =
2102 SparqlExecutor::from_frozen(FrozenIndexedDataset::from_graph(&Graph::new()), false);
2103 assert!(!executor.has_store());
2104 }
2105}
2106
2107#[cfg(test)]
2108mod batch_tests {
2109 use super::*;
2110 use shifty_algebra::SparqlQueryKind;
2111
2112 fn nn(s: &str) -> NamedNode {
2113 NamedNode::new(s).unwrap()
2114 }
2115
2116 fn subject(s: &str) -> Term {
2117 Term::NamedNode(nn(&format!("http://ex/{s}")))
2118 }
2119
2120 fn sample_graph() -> Graph {
2122 let mut g = Graph::new();
2123 for (s, v) in [("a", 5i64), ("b", 15), ("c", 25)] {
2124 g.insert(&Triple::new(
2125 nn(&format!("http://ex/{s}")),
2126 nn("http://ex/value"),
2127 Literal::from(v),
2128 ));
2129 }
2130 g
2131 }
2132
2133 fn foci() -> Vec<Term> {
2134 vec![subject("a"), subject("b"), subject("c")]
2135 }
2136
2137 fn per_focus(exec: &SparqlExecutor, c: &SparqlConstraint, foci: &[Term]) -> Vec<String> {
2139 let mut out = Vec::new();
2140 for f in foci {
2141 for v in exec.constraint_violations(c, f).unwrap() {
2142 out.push(format!("{f:?}|{:?}", v.value));
2143 }
2144 }
2145 out.sort();
2146 out
2147 }
2148
2149 #[test]
2150 fn batched_select_matches_per_focus() {
2151 let constraint = SparqlConstraint {
2154 kind: SparqlQueryKind::Select,
2155 query: "SELECT $this ?value WHERE { \
2156 $this <http://ex/value> ?value . \
2157 OPTIONAL { $this <http://ex/other> ?o } \
2158 FILTER(!bound(?o)) }"
2159 .to_string(),
2160 path: None,
2161 shape: None,
2162 messages: Vec::new(),
2163 extra_bindings: Vec::new(),
2164 bind_value_to_this: false,
2165 };
2166 let g = sample_graph();
2167 let foci = foci();
2168
2169 let baseline = SparqlExecutor::from_frozen(FrozenIndexedDataset::from_graph(&g), false);
2170 let want = per_focus(&baseline, &constraint, &foci);
2171
2172 let batched = SparqlExecutor::from_frozen(FrozenIndexedDataset::from_graph(&g), false);
2173 batched.prefetch_constraint(&constraint, &foci).unwrap();
2174 let got = per_focus(&batched, &constraint, &foci);
2175
2176 assert_eq!(want.len(), 3, "every subject should violate");
2177 assert_eq!(want, got);
2178 }
2179
2180 #[test]
2181 fn batched_ask_matches_per_focus() {
2182 let constraint = SparqlConstraint {
2184 kind: SparqlQueryKind::Ask,
2185 query: "ASK { $this <http://ex/value> ?value . \
2186 OPTIONAL { $this <http://ex/other> ?o } \
2187 FILTER(?value > 10) }"
2188 .to_string(),
2189 path: None,
2190 shape: None,
2191 messages: Vec::new(),
2192 extra_bindings: Vec::new(),
2193 bind_value_to_this: false,
2194 };
2195 let g = sample_graph();
2196 let foci = foci();
2197
2198 let baseline = SparqlExecutor::from_frozen(FrozenIndexedDataset::from_graph(&g), false);
2199 let want: Vec<usize> = foci
2200 .iter()
2201 .map(|f| {
2202 baseline
2203 .constraint_violations(&constraint, f)
2204 .unwrap()
2205 .len()
2206 })
2207 .collect();
2208
2209 let batched = SparqlExecutor::from_frozen(FrozenIndexedDataset::from_graph(&g), false);
2210 batched.prefetch_constraint(&constraint, &foci).unwrap();
2211 let got: Vec<usize> = foci
2212 .iter()
2213 .map(|f| batched.constraint_violations(&constraint, f).unwrap().len())
2214 .collect();
2215
2216 assert_eq!(want, vec![0, 1, 1]);
2217 assert_eq!(want, got);
2218 }
2219
2220 #[test]
2221 fn blank_node_foci_fall_through_to_per_focus() {
2222 use oxrdf::BlankNode;
2226 let mut g = Graph::new();
2227 let blank = BlankNode::default();
2228 g.insert(&Triple::new(
2230 nn("http://ex/a"),
2231 nn("http://ex/flag"),
2232 Literal::from(true),
2233 ));
2234 g.insert(&Triple::new(
2235 blank.clone(),
2236 nn("http://ex/flag"),
2237 Literal::from(true),
2238 ));
2239 g.insert(&Triple::new(
2241 nn("http://ex/x"),
2242 nn("http://ex/ref"),
2243 nn("http://ex/a"),
2244 ));
2245 g.insert(&Triple::new(
2246 nn("http://ex/y"),
2247 nn("http://ex/ref"),
2248 blank.clone(),
2249 ));
2250
2251 let constraint = SparqlConstraint {
2254 kind: SparqlQueryKind::Select,
2255 query: "SELECT ?s $this WHERE { \
2256 $this <http://ex/flag> true . \
2257 ?s <http://ex/ref> $this . \
2258 OPTIONAL { $this <http://ex/other> ?o } }"
2259 .to_string(),
2260 path: None,
2261 shape: None,
2262 messages: Vec::new(),
2263 extra_bindings: Vec::new(),
2264 bind_value_to_this: false,
2265 };
2266 let foci = vec![
2267 Term::NamedNode(nn("http://ex/a")),
2268 Term::BlankNode(blank),
2269 subject("c"),
2270 ];
2271
2272 let baseline = SparqlExecutor::from_frozen(FrozenIndexedDataset::from_graph(&g), false);
2273 let want = per_focus(&baseline, &constraint, &foci);
2274
2275 let batched = SparqlExecutor::from_frozen(FrozenIndexedDataset::from_graph(&g), false);
2276 batched.prefetch_constraint(&constraint, &foci).unwrap();
2277 let got = per_focus(&batched, &constraint, &foci);
2278
2279 assert!(!want.is_empty());
2283 assert_eq!(want, got);
2284 }
2285
2286 #[test]
2287 fn aggregating_query_is_not_batched_but_still_correct() {
2288 let constraint = SparqlConstraint {
2291 kind: SparqlQueryKind::Select,
2292 query: "SELECT $this (COUNT(?value) AS ?n) WHERE { \
2293 $this <http://ex/value> ?value } \
2294 GROUP BY $this HAVING (COUNT(?value) > 5)"
2295 .to_string(),
2296 path: None,
2297 shape: None,
2298 messages: Vec::new(),
2299 extra_bindings: Vec::new(),
2300 bind_value_to_this: false,
2301 };
2302 let g = sample_graph();
2303 let foci = foci();
2304
2305 let baseline = SparqlExecutor::from_frozen(FrozenIndexedDataset::from_graph(&g), false);
2306 let want = per_focus(&baseline, &constraint, &foci);
2307
2308 let batched = SparqlExecutor::from_frozen(FrozenIndexedDataset::from_graph(&g), false);
2309 batched.prefetch_constraint(&constraint, &foci).unwrap();
2310 let got = per_focus(&batched, &constraint, &foci);
2311
2312 assert_eq!(want, got); }
2314
2315 #[test]
2316 fn batched_construct_matches_per_focus() {
2317 let query = "CONSTRUCT { $this <http://ex/uniqueKind> ?k } WHERE { \
2323 { SELECT $this (COUNT(DISTINCT ?k0) AS ?c) \
2324 WHERE { $this <http://ex/reads>/<http://ex/kind> ?k0 } GROUP BY $this } \
2325 FILTER(?c = 1) \
2326 $this <http://ex/reads>/<http://ex/kind> ?k }";
2327
2328 let mut g = Graph::new();
2329 let mut edge = |s: &str, p: &str, o: &str| {
2330 g.insert(&Triple::new(
2331 nn(&format!("http://ex/{s}")),
2332 nn(&format!("http://ex/{p}")),
2333 nn(&format!("http://ex/{o}")),
2334 ));
2335 };
2336 edge("a", "reads", "pa1");
2337 edge("pa1", "kind", "Temp");
2338 edge("a", "reads", "pa2");
2339 edge("pa2", "kind", "Temp");
2340 edge("b", "reads", "qb1");
2341 edge("qb1", "kind", "Temp");
2342 edge("b", "reads", "qb2");
2343 edge("qb2", "kind", "Flow");
2344
2345 let foci = vec![subject("a"), subject("b")];
2346 let sorted = |mut v: Vec<Triple>| -> Vec<Triple> {
2347 v.sort_by_key(|t| t.to_string());
2348 v
2349 };
2350
2351 let baseline = SparqlExecutor::new(&g).unwrap();
2354 let want = sorted(baseline.construct_many(query, &foci, None).unwrap());
2355
2356 let batched = SparqlExecutor::new(&g).unwrap();
2358 batched
2359 .batch_construct
2360 .borrow_mut()
2361 .insert(query.to_string());
2362 let got = sorted(batched.construct_many(query, &foci, None).unwrap());
2363
2364 let expected = Triple::new(
2365 nn("http://ex/a"),
2366 nn("http://ex/uniqueKind"),
2367 nn("http://ex/Temp"),
2368 );
2369 assert_eq!(
2370 want,
2371 vec![expected],
2372 "per-focus oracle: only ex:a is inferred"
2373 );
2374 assert_eq!(got, want, "batched output must match per-focus exactly");
2375 }
2376}