1use crate::model::*;
6use crate::query::algebra::*;
7use crate::query::plan::ExecutionPlan;
8use crate::OxirsError;
9use crate::Store;
10use std::collections::{HashMap, HashSet};
11
12#[derive(Debug, Clone, PartialEq)]
14pub struct Solution {
15 bindings: HashMap<Variable, Term>,
16}
17
18impl Solution {
19 pub fn new() -> Self {
21 Solution {
22 bindings: HashMap::new(),
23 }
24 }
25
26 pub fn bind(&mut self, var: Variable, value: Term) {
28 self.bindings.insert(var, value);
29 }
30
31 pub fn get(&self, var: &Variable) -> Option<&Term> {
33 self.bindings.get(var)
34 }
35
36 pub fn merge(&self, other: &Solution) -> Option<Solution> {
38 let mut merged = self.clone();
39
40 for (var, value) in &other.bindings {
41 if let Some(existing) = merged.bindings.get(var) {
42 if existing != value {
43 return None; }
45 } else {
46 merged.bindings.insert(var.clone(), value.clone());
47 }
48 }
49
50 Some(merged)
51 }
52
53 pub fn project(&self, vars: &[Variable]) -> Solution {
55 let mut projected = Solution::new();
56 for var in vars {
57 if let Some(value) = self.bindings.get(var) {
58 projected.bind(var.clone(), value.clone());
59 }
60 }
61 projected
62 }
63
64 pub fn iter(&self) -> std::collections::hash_map::Iter<'_, Variable, Term> {
66 self.bindings.iter()
67 }
68
69 pub fn variables(&self) -> impl Iterator<Item = &Variable> {
71 self.bindings.keys()
72 }
73
74 pub fn canonical_key(&self) -> Vec<(String, String)> {
82 let mut pairs: Vec<(String, String)> = self
83 .bindings
84 .iter()
85 .map(|(var, term)| (var.name().to_string(), term.to_string()))
86 .collect();
87 pairs.sort();
88 pairs
89 }
90}
91
92#[derive(Debug)]
94pub enum QueryResults {
95 Boolean(bool),
97 Solutions(Vec<Solution>),
99 Graph(Vec<Triple>),
101}
102
103pub struct QueryExecutor<'a> {
105 store: &'a dyn Store,
106}
107
108impl<'a> QueryExecutor<'a> {
109 pub fn new(store: &'a dyn Store) -> Self {
111 QueryExecutor { store }
112 }
113
114 pub fn execute(&self, plan: &ExecutionPlan) -> Result<Vec<Solution>, OxirsError> {
116 self.execute_plan(plan)
117 }
118
119 fn execute_plan(&self, plan: &ExecutionPlan) -> Result<Vec<Solution>, OxirsError> {
120 match plan {
121 ExecutionPlan::TripleScan { pattern } => self.execute_triple_scan(pattern),
122 ExecutionPlan::HashJoin {
123 left,
124 right,
125 join_vars,
126 } => self.execute_hash_join(left, right, join_vars),
127 ExecutionPlan::Filter { input, condition } => self.execute_filter(input, condition),
128 ExecutionPlan::Project { input, vars } => self.execute_project(input, vars),
129 ExecutionPlan::Sort { input, order_by } => self.execute_sort(input, order_by),
130 ExecutionPlan::Limit {
131 input,
132 limit,
133 offset,
134 } => self.execute_limit(input, *limit, *offset),
135 ExecutionPlan::Union { left, right } => self.execute_union(left, right),
136 ExecutionPlan::Distinct { input } => self.execute_distinct(input),
137 }
138 }
139
140 fn execute_triple_scan(
141 &self,
142 pattern: &crate::model::pattern::TriplePattern,
143 ) -> Result<Vec<Solution>, OxirsError> {
144 let mut solutions = Vec::new();
145
146 let quads = self.store.default_graph_quads()?;
152
153 for quad in quads {
154 let triple = Triple::new(
155 quad.subject().clone(),
156 quad.predicate().clone(),
157 quad.object().clone(),
158 );
159 if let Some(solution) = self.match_triple_pattern(&triple, pattern) {
160 solutions.push(solution);
161 }
162 }
163
164 Ok(solutions)
165 }
166
167 fn match_triple_pattern(
168 &self,
169 triple: &Triple,
170 pattern: &crate::model::pattern::TriplePattern,
171 ) -> Option<Solution> {
172 let mut solution = Solution::new();
173
174 if let Some(ref subject_pattern) = pattern.subject {
176 if !self.match_subject_pattern(triple.subject(), subject_pattern, &mut solution) {
177 return None;
178 }
179 }
180
181 if let Some(ref predicate_pattern) = pattern.predicate {
183 if !self.match_predicate_pattern(triple.predicate(), predicate_pattern, &mut solution) {
184 return None;
185 }
186 }
187
188 if let Some(ref object_pattern) = pattern.object {
190 if !self.match_object_pattern(triple.object(), object_pattern, &mut solution) {
191 return None;
192 }
193 }
194
195 Some(solution)
196 }
197
198 #[allow(dead_code)]
199 fn match_term_pattern(
200 &self,
201 term: &Term,
202 pattern: &TermPattern,
203 solution: &mut Solution,
204 ) -> bool {
205 match pattern {
206 TermPattern::Variable(var) => {
207 if let Some(bound_value) = solution.get(var) {
208 bound_value == term
209 } else {
210 solution.bind(var.clone(), term.clone());
211 true
212 }
213 }
214 TermPattern::NamedNode(n) => {
215 matches!(term, Term::NamedNode(nn) if nn == n)
216 }
217 TermPattern::BlankNode(b) => {
218 matches!(term, Term::BlankNode(bn) if bn == b)
219 }
220 TermPattern::Literal(l) => {
221 matches!(term, Term::Literal(lit) if lit == l)
222 }
223 TermPattern::QuotedTriple(_) => {
224 false
229 }
230 }
231 }
232
233 fn match_subject_pattern(
234 &self,
235 subject: &Subject,
236 pattern: &crate::model::pattern::SubjectPattern,
237 solution: &mut Solution,
238 ) -> bool {
239 use crate::model::pattern::SubjectPattern;
240 match pattern {
241 SubjectPattern::Variable(var) => {
242 if let Some(bound_value) = solution.get(var) {
243 match (subject, bound_value) {
244 (Subject::NamedNode(n1), Term::NamedNode(n2)) => n1 == n2,
245 (Subject::BlankNode(b1), Term::BlankNode(b2)) => b1 == b2,
246 _ => false,
247 }
248 } else {
249 solution
250 .bindings
251 .insert(var.clone(), Term::from_subject(subject));
252 true
253 }
254 }
255 SubjectPattern::NamedNode(n) => matches!(subject, Subject::NamedNode(nn) if nn == n),
256 SubjectPattern::BlankNode(b) => matches!(subject, Subject::BlankNode(bn) if bn == b),
257 SubjectPattern::QuotedTriple(_) => matches!(subject, Subject::QuotedTriple(_)),
260 }
261 }
262
263 fn match_predicate_pattern(
264 &self,
265 predicate: &Predicate,
266 pattern: &crate::model::pattern::PredicatePattern,
267 solution: &mut Solution,
268 ) -> bool {
269 use crate::model::pattern::PredicatePattern;
270 match pattern {
271 PredicatePattern::Variable(var) => {
272 if let Some(bound_value) = solution.get(var) {
273 match (predicate, bound_value) {
274 (Predicate::NamedNode(n1), Term::NamedNode(n2)) => n1 == n2,
275 _ => false,
276 }
277 } else {
278 solution
279 .bindings
280 .insert(var.clone(), Term::from_predicate(predicate));
281 true
282 }
283 }
284 PredicatePattern::NamedNode(n) => {
285 matches!(predicate, Predicate::NamedNode(nn) if nn == n)
286 }
287 }
288 }
289
290 fn match_object_pattern(
291 &self,
292 object: &Object,
293 pattern: &crate::model::pattern::ObjectPattern,
294 solution: &mut Solution,
295 ) -> bool {
296 use crate::model::pattern::ObjectPattern;
297 match pattern {
298 ObjectPattern::Variable(var) => {
299 if let Some(bound_value) = solution.get(var) {
300 match (object, bound_value) {
301 (Object::NamedNode(n1), Term::NamedNode(n2)) => n1 == n2,
302 (Object::BlankNode(b1), Term::BlankNode(b2)) => b1 == b2,
303 (Object::Literal(l1), Term::Literal(l2)) => l1 == l2,
304 _ => false,
305 }
306 } else {
307 solution
308 .bindings
309 .insert(var.clone(), Term::from_object(object));
310 true
311 }
312 }
313 ObjectPattern::NamedNode(n) => matches!(object, Object::NamedNode(nn) if nn == n),
314 ObjectPattern::BlankNode(b) => matches!(object, Object::BlankNode(bn) if bn == b),
315 ObjectPattern::Literal(l) => matches!(object, Object::Literal(lit) if lit == l),
316 ObjectPattern::QuotedTriple(_) => matches!(object, Object::QuotedTriple(_)),
318 }
319 }
320
321 fn execute_hash_join(
322 &self,
323 left: &ExecutionPlan,
324 right: &ExecutionPlan,
325 join_vars: &[Variable],
326 ) -> Result<Vec<Solution>, OxirsError> {
327 let left_solutions = self.execute_plan(left)?;
328 let right_solutions = self.execute_plan(right)?;
329
330 let mut results = Vec::new();
331
332 let mut hash_table: HashMap<Vec<Term>, Vec<Solution>> = HashMap::new();
334 for solution in left_solutions {
335 let key: Vec<Term> = join_vars
336 .iter()
337 .filter_map(|var| solution.get(var).cloned())
338 .collect();
339 hash_table.entry(key).or_default().push(solution);
340 }
341
342 for right_solution in right_solutions {
344 let key: Vec<Term> = join_vars
345 .iter()
346 .filter_map(|var| right_solution.get(var).cloned())
347 .collect();
348
349 if let Some(left_solutions) = hash_table.get(&key) {
350 for left_solution in left_solutions {
351 if let Some(merged) = left_solution.merge(&right_solution) {
352 results.push(merged);
353 }
354 }
355 }
356 }
357
358 Ok(results)
359 }
360
361 fn execute_filter(
362 &self,
363 input: &ExecutionPlan,
364 condition: &Expression,
365 ) -> Result<Vec<Solution>, OxirsError> {
366 let solutions = self.execute_plan(input)?;
367
368 Ok(solutions
369 .into_iter()
370 .filter(|solution| {
371 self.evaluate_expression(condition, solution)
372 .unwrap_or(false)
373 })
374 .collect())
375 }
376
377 fn execute_project(
378 &self,
379 input: &ExecutionPlan,
380 vars: &[Variable],
381 ) -> Result<Vec<Solution>, OxirsError> {
382 let solutions = self.execute_plan(input)?;
383
384 Ok(solutions
385 .into_iter()
386 .map(|solution| solution.project(vars))
387 .collect())
388 }
389
390 fn execute_sort(
391 &self,
392 input: &ExecutionPlan,
393 order_by: &[OrderExpression],
394 ) -> Result<Vec<Solution>, OxirsError> {
395 let mut solutions = self.execute_plan(input)?;
396
397 solutions.sort_by(|a, b| {
399 for order in order_by {
400 let (expr, descending) = match order {
401 OrderExpression::Asc(e) => (e, false),
402 OrderExpression::Desc(e) => (e, true),
403 };
404 let ta = self.evaluate_expression_to_term(expr, a);
405 let tb = self.evaluate_expression_to_term(expr, b);
406 let mut ord = Self::order_compare(ta.as_ref(), tb.as_ref());
407 if descending {
408 ord = ord.reverse();
409 }
410 if ord != std::cmp::Ordering::Equal {
411 return ord;
412 }
413 }
414 std::cmp::Ordering::Equal
415 });
416
417 Ok(solutions)
418 }
419
420 fn order_compare(a: Option<&Term>, b: Option<&Term>) -> std::cmp::Ordering {
426 use std::cmp::Ordering;
427 match (a, b) {
428 (None, None) => Ordering::Equal,
429 (None, Some(_)) => Ordering::Less,
430 (Some(_), None) => Ordering::Greater,
431 (Some(a), Some(b)) => {
432 if let Some(ord) = Self::compare_terms(a, b) {
433 return ord;
434 }
435 Self::term_kind_rank(a)
436 .cmp(&Self::term_kind_rank(b))
437 .then_with(|| a.to_string().cmp(&b.to_string()))
438 }
439 }
440 }
441
442 fn term_kind_rank(term: &Term) -> u8 {
444 match term {
445 Term::BlankNode(_) => 0,
446 Term::NamedNode(_) => 1,
447 Term::Literal(_) => 2,
448 _ => 3,
449 }
450 }
451
452 fn execute_limit(
453 &self,
454 input: &ExecutionPlan,
455 limit: usize,
456 offset: usize,
457 ) -> Result<Vec<Solution>, OxirsError> {
458 let solutions = self.execute_plan(input)?;
459
460 Ok(solutions.into_iter().skip(offset).take(limit).collect())
461 }
462
463 fn execute_union(
464 &self,
465 left: &ExecutionPlan,
466 right: &ExecutionPlan,
467 ) -> Result<Vec<Solution>, OxirsError> {
468 let mut solutions = self.execute_plan(left)?;
469 solutions.extend(self.execute_plan(right)?);
470 Ok(solutions)
471 }
472
473 fn execute_distinct(&self, input: &ExecutionPlan) -> Result<Vec<Solution>, OxirsError> {
474 let solutions = self.execute_plan(input)?;
475 let mut seen = HashSet::new();
476 let mut distinct_solutions = Vec::new();
477
478 for solution in solutions {
479 if seen.insert(solution.canonical_key()) {
486 distinct_solutions.push(solution);
487 }
488 }
489
490 Ok(distinct_solutions)
491 }
492
493 fn evaluate_expression(&self, expr: &Expression, solution: &Solution) -> Option<bool> {
494 match expr {
495 Expression::Variable(var) => {
496 if let Some(term) = solution.get(var) {
497 match term {
499 Term::Literal(lit) => {
500 let value = lit.as_str();
501 match lit.datatype().as_str() {
502 "http://www.w3.org/2001/XMLSchema#boolean" => {
503 value.parse::<bool>().ok()
504 }
505 "http://www.w3.org/2001/XMLSchema#integer"
506 | "http://www.w3.org/2001/XMLSchema#decimal"
507 | "http://www.w3.org/2001/XMLSchema#double" => {
508 value.parse::<f64>().map(|n| n != 0.0).ok()
509 }
510 "http://www.w3.org/2001/XMLSchema#string" => {
511 Some(!value.is_empty())
512 }
513 _ => Some(!value.is_empty()),
514 }
515 }
516 _ => Some(true), }
518 } else {
519 Some(false) }
521 }
522 Expression::Literal(lit) => {
523 let value = lit.as_str();
524 match lit.datatype().as_str() {
525 "http://www.w3.org/2001/XMLSchema#boolean" => value.parse::<bool>().ok(),
526 "http://www.w3.org/2001/XMLSchema#integer"
527 | "http://www.w3.org/2001/XMLSchema#decimal"
528 | "http://www.w3.org/2001/XMLSchema#double" => {
529 value.parse::<f64>().map(|n| n != 0.0).ok()
530 }
531 _ => Some(!value.is_empty()),
532 }
533 }
534 Expression::And(left, right) => {
535 let left_result = self.evaluate_expression(left, solution)?;
536 let right_result = self.evaluate_expression(right, solution)?;
537 Some(left_result && right_result)
538 }
539 Expression::Or(left, right) => {
540 let left_result = self.evaluate_expression(left, solution)?;
541 let right_result = self.evaluate_expression(right, solution)?;
542 Some(left_result || right_result)
543 }
544 Expression::Not(expr) => {
545 let result = self.evaluate_expression(expr, solution)?;
546 Some(!result)
547 }
548 Expression::Equal(left, right) => {
549 let left_term = self.evaluate_expression_to_term(left, solution)?;
550 let right_term = self.evaluate_expression_to_term(right, solution)?;
551 Some(left_term == right_term)
552 }
553 Expression::NotEqual(left, right) => {
554 let left_term = self.evaluate_expression_to_term(left, solution)?;
555 let right_term = self.evaluate_expression_to_term(right, solution)?;
556 Some(left_term != right_term)
557 }
558 Expression::Less(left, right) => self
559 .compare_terms_expr(left, right, solution)
560 .map(|ord| ord == std::cmp::Ordering::Less),
561 Expression::LessOrEqual(left, right) => self
562 .compare_terms_expr(left, right, solution)
563 .map(|ord| ord != std::cmp::Ordering::Greater),
564 Expression::Greater(left, right) => self
565 .compare_terms_expr(left, right, solution)
566 .map(|ord| ord == std::cmp::Ordering::Greater),
567 Expression::GreaterOrEqual(left, right) => self
568 .compare_terms_expr(left, right, solution)
569 .map(|ord| ord != std::cmp::Ordering::Less),
570 Expression::Bound(var) => Some(solution.get(var).is_some()),
571 Expression::IsIri(expr) => {
572 if let Some(term) = self.evaluate_expression_to_term(expr, solution) {
573 Some(matches!(term, Term::NamedNode(_)))
574 } else {
575 Some(false)
576 }
577 }
578 Expression::IsBlank(expr) => {
579 if let Some(term) = self.evaluate_expression_to_term(expr, solution) {
580 Some(matches!(term, Term::BlankNode(_)))
581 } else {
582 Some(false)
583 }
584 }
585 Expression::IsLiteral(expr) => {
586 if let Some(term) = self.evaluate_expression_to_term(expr, solution) {
587 Some(matches!(term, Term::Literal(_)))
588 } else {
589 Some(false)
590 }
591 }
592 Expression::IsNumeric(expr) => {
593 if let Some(Term::Literal(lit)) = self.evaluate_expression_to_term(expr, solution) {
594 let datatype_str = lit.datatype().as_str().to_string();
595 Some(matches!(
596 datatype_str.as_str(),
597 "http://www.w3.org/2001/XMLSchema#integer"
598 | "http://www.w3.org/2001/XMLSchema#decimal"
599 | "http://www.w3.org/2001/XMLSchema#double"
600 | "http://www.w3.org/2001/XMLSchema#float"
601 ))
602 } else {
603 Some(false)
604 }
605 }
606 Expression::Str(expr) => {
607 Some(self.evaluate_expression_to_term(expr, solution).is_some())
609 }
610 Expression::Regex(text_expr, pattern_expr, flags_expr) => {
611 let text = self.evaluate_expression_to_string(text_expr, solution)?;
612 let pattern = self.evaluate_expression_to_string(pattern_expr, solution)?;
613
614 let flags = if let Some(flags_expr) = flags_expr {
615 self.evaluate_expression_to_string(flags_expr, solution)
616 .unwrap_or_default()
617 } else {
618 String::new()
619 };
620
621 if flags.is_empty() {
623 Some(text.contains(&pattern))
624 } else {
625 if flags.contains('i') {
627 Some(text.to_lowercase().contains(&pattern.to_lowercase()))
628 } else {
629 Some(text.contains(&pattern))
630 }
631 }
632 }
633 _ => {
634 Some(true)
637 }
638 }
639 }
640
641 #[allow(clippy::only_used_in_recursion)]
643 fn evaluate_expression_to_term(&self, expr: &Expression, solution: &Solution) -> Option<Term> {
644 match expr {
645 Expression::Variable(var) => solution.get(var).cloned(),
646 Expression::Term(term) => Some(term.clone()),
647 Expression::FunctionCall(Function::Str, args) => {
648 if let Some(arg) = args.first() {
649 if let Some(term) = self.evaluate_expression_to_term(arg, solution) {
650 match term {
651 Term::NamedNode(n) => Some(Term::Literal(Literal::new(n.as_str()))),
652 Term::Literal(l) => Some(Term::Literal(Literal::new(l.as_str()))),
653 Term::BlankNode(b) => Some(Term::Literal(Literal::new(b.as_str()))),
654 _ => None,
655 }
656 } else {
657 None
658 }
659 } else {
660 None
661 }
662 }
663 _ => None, }
665 }
666
667 fn evaluate_expression_to_string(
669 &self,
670 expr: &Expression,
671 solution: &Solution,
672 ) -> Option<String> {
673 if let Some(term) = self.evaluate_expression_to_term(expr, solution) {
674 match term {
675 Term::NamedNode(n) => Some(n.as_str().to_string()),
676 Term::Literal(l) => Some(l.as_str().to_string()),
677 Term::BlankNode(b) => Some(b.as_str().to_string()),
678 _ => None,
679 }
680 } else {
681 None
682 }
683 }
684
685 fn compare_terms_expr(
698 &self,
699 left: &Expression,
700 right: &Expression,
701 solution: &Solution,
702 ) -> Option<std::cmp::Ordering> {
703 let left_term = self.evaluate_expression_to_term(left, solution)?;
704 let right_term = self.evaluate_expression_to_term(right, solution)?;
705 Self::compare_terms(&left_term, &right_term)
706 }
707
708 fn compare_terms(left: &Term, right: &Term) -> Option<std::cmp::Ordering> {
713 let (l, r) = match (left, right) {
714 (Term::Literal(l), Term::Literal(r)) => (l, r),
715 _ => return None,
717 };
718
719 let l_dt = l.datatype().as_str().to_string();
720 let r_dt = r.datatype().as_str().to_string();
721
722 if let (Some(a), Some(b)) = (
724 Self::numeric_value(&l_dt, l.as_str()),
725 Self::numeric_value(&r_dt, r.as_str()),
726 ) {
727 return a.partial_cmp(&b);
728 }
729
730 if l_dt != r_dt {
733 return None;
734 }
735
736 match l_dt.as_str() {
737 "http://www.w3.org/2001/XMLSchema#string" => Some(l.as_str().cmp(r.as_str())),
738 "http://www.w3.org/2001/XMLSchema#boolean" => {
739 let a = Self::boolean_value(l.as_str())?;
740 let b = Self::boolean_value(r.as_str())?;
741 Some(a.cmp(&b))
742 }
743 "http://www.w3.org/2001/XMLSchema#dateTime" => {
744 use std::str::FromStr;
745 let a = oxsdatatypes::DateTime::from_str(l.as_str()).ok()?;
746 let b = oxsdatatypes::DateTime::from_str(r.as_str()).ok()?;
747 a.partial_cmp(&b)
748 }
749 "http://www.w3.org/2001/XMLSchema#date" => {
750 use std::str::FromStr;
751 let a = oxsdatatypes::Date::from_str(l.as_str()).ok()?;
752 let b = oxsdatatypes::Date::from_str(r.as_str()).ok()?;
753 a.partial_cmp(&b)
754 }
755 _ => None,
756 }
757 }
758
759 fn numeric_value(datatype: &str, value: &str) -> Option<f64> {
762 match datatype {
763 "http://www.w3.org/2001/XMLSchema#integer"
764 | "http://www.w3.org/2001/XMLSchema#decimal"
765 | "http://www.w3.org/2001/XMLSchema#double"
766 | "http://www.w3.org/2001/XMLSchema#float" => value.parse::<f64>().ok(),
767 _ => None,
768 }
769 }
770
771 fn boolean_value(value: &str) -> Option<bool> {
773 match value {
774 "true" | "1" => Some(true),
775 "false" | "0" => Some(false),
776 _ => None,
777 }
778 }
779}
780
781impl Default for Solution {
782 fn default() -> Self {
783 Self::new()
784 }
785}
786
787#[cfg(test)]
788mod tests {
789 use crate::model::{GraphName, Literal, NamedNode, Object, Predicate, Quad, Subject};
790 use crate::query::{QueryEngine, QueryResult};
791 use crate::rdf_store::RdfStore;
792 use crate::Store;
793
794 fn iri(s: &str) -> NamedNode {
795 NamedNode::new(s).expect("valid iri")
796 }
797
798 fn count_bindings(result: &QueryResult) -> usize {
799 match result {
800 QueryResult::Select { bindings, .. } => bindings.len(),
801 _ => panic!("expected SELECT result"),
802 }
803 }
804
805 #[test]
808 fn regression_bgp_scans_default_graph_only() {
809 let store = RdfStore::new().expect("store");
810 store
812 .insert_quad(Quad::new(
813 Subject::NamedNode(iri("http://example.org/s1")),
814 Predicate::NamedNode(iri("http://example.org/p")),
815 Object::NamedNode(iri("http://example.org/o1")),
816 GraphName::DefaultGraph,
817 ))
818 .expect("insert default");
819 store
821 .insert_quad(Quad::new(
822 Subject::NamedNode(iri("http://example.org/s2")),
823 Predicate::NamedNode(iri("http://example.org/p")),
824 Object::NamedNode(iri("http://example.org/o2")),
825 GraphName::NamedNode(iri("http://example.org/g")),
826 ))
827 .expect("insert named");
828
829 let engine = QueryEngine::new();
830 let result = engine
831 .query("SELECT * WHERE { ?s ?p ?o . }", &store)
832 .expect("query ok");
833 assert_eq!(
834 count_bindings(&result),
835 1,
836 "only the default-graph triple must match a default-graph BGP"
837 );
838 }
839
840 #[test]
842 fn regression_distinct_multi_variable_dedup() {
843 let store = RdfStore::new().expect("store");
844 let p = iri("http://example.org/p");
845 let o = iri("http://example.org/o");
846 for s in ["http://example.org/s1", "http://example.org/s2"] {
848 store
849 .insert_quad(Quad::new(
850 Subject::NamedNode(iri(s)),
851 Predicate::NamedNode(p.clone()),
852 Object::NamedNode(o.clone()),
853 GraphName::DefaultGraph,
854 ))
855 .expect("insert");
856 }
857
858 let engine = QueryEngine::new();
859 let result = engine
860 .query("SELECT DISTINCT ?p ?o WHERE { ?s ?p ?o . }", &store)
861 .expect("query ok");
862 assert_eq!(
863 count_bindings(&result),
864 1,
865 "identical (p, o) bindings must collapse to a single DISTINCT row"
866 );
867 }
868
869 #[test]
872 fn regression_filter_string_comparison() {
873 let store = RdfStore::new().expect("store");
874 let p = iri("http://example.org/name");
875 for (s, name) in [
876 ("http://example.org/a", "apple"),
877 ("http://example.org/z", "zebra"),
878 ] {
879 store
880 .insert_quad(Quad::new(
881 Subject::NamedNode(iri(s)),
882 Predicate::NamedNode(p.clone()),
883 Object::Literal(Literal::new(name)),
884 GraphName::DefaultGraph,
885 ))
886 .expect("insert");
887 }
888
889 let engine = QueryEngine::new();
890 let result = engine
891 .query(
892 "SELECT ?name WHERE { ?s ?p ?name . FILTER(?name > \"m\") }",
893 &store,
894 )
895 .expect("query ok");
896 assert_eq!(
897 count_bindings(&result),
898 1,
899 "only 'zebra' is greater than 'm'"
900 );
901 }
902
903 #[test]
905 fn regression_filter_date_comparison() {
906 let store = RdfStore::new().expect("store");
907 let p = iri("http://example.org/born");
908 let date_dt = crate::vocab::xsd::DATE.clone();
909 for (s, d) in [
910 ("http://example.org/old", "1990-01-01"),
911 ("http://example.org/new", "2020-01-01"),
912 ] {
913 store
914 .insert_quad(Quad::new(
915 Subject::NamedNode(iri(s)),
916 Predicate::NamedNode(p.clone()),
917 Object::Literal(Literal::new_typed(d, date_dt.clone())),
918 GraphName::DefaultGraph,
919 ))
920 .expect("insert");
921 }
922
923 let engine = QueryEngine::new();
924 let result = engine
925 .query(
926 "SELECT ?d WHERE { ?s ?p ?d . FILTER(?d < \"2000-01-01\"^^<http://www.w3.org/2001/XMLSchema#date>) }",
927 &store,
928 )
929 .expect("query ok");
930 assert_eq!(
931 count_bindings(&result),
932 1,
933 "only the 1990 date is before 2000"
934 );
935 }
936
937 #[test]
939 fn regression_order_by_sorts_results() {
940 let store = RdfStore::new().expect("store");
941 let p = iri("http://example.org/n");
942 let int_dt = crate::vocab::xsd::INTEGER.clone();
943 for (s, n) in [
944 ("http://example.org/b", "3"),
945 ("http://example.org/a", "1"),
946 ("http://example.org/c", "2"),
947 ] {
948 store
949 .insert_quad(Quad::new(
950 Subject::NamedNode(iri(s)),
951 Predicate::NamedNode(p.clone()),
952 Object::Literal(Literal::new_typed(n, int_dt.clone())),
953 GraphName::DefaultGraph,
954 ))
955 .expect("insert");
956 }
957
958 let engine = QueryEngine::new();
959 let result = engine
960 .query("SELECT ?n WHERE { ?s ?p ?n . } ORDER BY ?n", &store)
961 .expect("query ok");
962 let QueryResult::Select { bindings, .. } = result else {
963 panic!("expected SELECT");
964 };
965 let values: Vec<String> = bindings
966 .iter()
967 .filter_map(|b| b.get("n"))
968 .map(|t| t.to_string())
969 .collect();
970 let sorted_positions: Vec<&String> = values.iter().collect();
971 assert_eq!(values.len(), 3);
973 assert!(
974 sorted_positions[0].contains('1')
975 && sorted_positions[1].contains('2')
976 && sorted_positions[2].contains('3'),
977 "ORDER BY ?n should yield 1,2,3 — got {values:?}"
978 );
979 }
980
981 #[test]
983 fn regression_limit_offset_applied() {
984 let store = RdfStore::new().expect("store");
985 let p = iri("http://example.org/n");
986 let int_dt = crate::vocab::xsd::INTEGER.clone();
987 for n in 0..5 {
988 store
989 .insert_quad(Quad::new(
990 Subject::NamedNode(iri(&format!("http://example.org/s{n}"))),
991 Predicate::NamedNode(p.clone()),
992 Object::Literal(Literal::new_typed(n.to_string(), int_dt.clone())),
993 GraphName::DefaultGraph,
994 ))
995 .expect("insert");
996 }
997
998 let engine = QueryEngine::new();
999 let result = engine
1000 .query(
1001 "SELECT ?n WHERE { ?s ?p ?n . } ORDER BY ?n LIMIT 2 OFFSET 1",
1002 &store,
1003 )
1004 .expect("query ok");
1005 assert_eq!(
1006 count_bindings(&result),
1007 2,
1008 "LIMIT 2 must cap the result set"
1009 );
1010 }
1011}