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