1use std::collections::{HashMap, HashSet};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct TriplePattern {
11 pub s: String,
12 pub p: String,
13 pub o: String,
14}
15
16impl TriplePattern {
17 pub fn new(s: impl Into<String>, p: impl Into<String>, o: impl Into<String>) -> Self {
19 Self {
20 s: s.into(),
21 p: p.into(),
22 o: o.into(),
23 }
24 }
25
26 pub fn variables(&self) -> Vec<String> {
28 let mut vars = Vec::new();
29 for term in [&self.s, &self.p, &self.o] {
30 if let Some(var) = term.strip_prefix('?') {
31 vars.push(var.to_string());
32 }
33 }
34 vars
35 }
36}
37
38#[derive(Debug, Clone)]
40pub struct SelectQuery {
41 pub select_vars: Vec<String>,
43 pub where_clause: Vec<TriplePattern>,
45 pub filters: Vec<String>,
47 pub limit: Option<usize>,
49 pub offset: Option<usize>,
51 pub distinct: bool,
53}
54
55impl SelectQuery {
56 pub fn new() -> Self {
58 Self {
59 select_vars: Vec::new(),
60 where_clause: Vec::new(),
61 filters: Vec::new(),
62 limit: None,
63 offset: None,
64 distinct: false,
65 }
66 }
67
68 pub fn select(mut self, vars: &[&str]) -> Self {
70 self.select_vars = vars.iter().map(|v| v.to_string()).collect();
71 self
72 }
73
74 pub fn where_triple(mut self, s: &str, p: &str, o: &str) -> Self {
76 self.where_clause.push(TriplePattern::new(s, p, o));
77 self
78 }
79
80 pub fn filter(mut self, f: impl Into<String>) -> Self {
82 self.filters.push(f.into());
83 self
84 }
85
86 pub fn limit(mut self, n: usize) -> Self {
88 self.limit = Some(n);
89 self
90 }
91
92 pub fn offset(mut self, n: usize) -> Self {
94 self.offset = Some(n);
95 self
96 }
97
98 pub fn distinct(mut self) -> Self {
100 self.distinct = true;
101 self
102 }
103}
104
105impl Default for SelectQuery {
106 fn default() -> Self {
107 Self::new()
108 }
109}
110
111#[derive(Debug, Clone)]
113pub struct Subquery {
114 pub inner: Box<SelectQuery>,
115 pub outer_vars: Vec<String>,
117}
118
119impl Subquery {
120 pub fn new(inner: SelectQuery, outer_vars: Vec<String>) -> Self {
122 Self {
123 inner: Box::new(inner),
124 outer_vars,
125 }
126 }
127}
128
129#[derive(Debug, Clone)]
131pub struct SubqueryResult {
132 pub bindings: Vec<HashMap<String, String>>,
134 pub var_count: usize,
136}
137
138impl SubqueryResult {
139 pub fn new(bindings: Vec<HashMap<String, String>>) -> Self {
140 let var_count = bindings.first().map(|b| b.len()).unwrap_or(0);
141 Self {
142 bindings,
143 var_count,
144 }
145 }
146}
147
148pub struct SubqueryExecutor;
150
151impl SubqueryExecutor {
152 pub fn new() -> Self {
154 Self
155 }
156
157 pub fn execute(
167 &self,
168 subquery: &Subquery,
169 outer_bindings: &[HashMap<String, String>],
170 ) -> SubqueryResult {
171 let inner = &subquery.inner;
172 let is_correlated = Self::is_correlated(subquery);
173
174 let mut all_results: Vec<HashMap<String, String>> = Vec::new();
176
177 let base_outer: Vec<HashMap<String, String>> = if outer_bindings.is_empty() {
178 vec![HashMap::new()]
179 } else {
180 outer_bindings.to_vec()
181 };
182
183 for outer in &base_outer {
184 let candidates = self.evaluate_where_clause(inner, outer, is_correlated);
185
186 let filtered: Vec<_> = candidates
188 .into_iter()
189 .filter(|binding| self.apply_filters(&inner.filters, binding))
190 .collect();
191
192 all_results.extend(filtered);
193 }
194
195 let mut projected = Self::project(all_results, &inner.select_vars);
197
198 if inner.distinct {
200 projected = Self::deduplicate(projected);
201 }
202
203 projected = Self::apply_limit_offset(projected, inner.limit, inner.offset);
205
206 let var_count = projected.first().map(|b| b.len()).unwrap_or(0);
207
208 SubqueryResult {
209 bindings: projected,
210 var_count,
211 }
212 }
213
214 fn evaluate_where_clause(
217 &self,
218 query: &SelectQuery,
219 outer: &HashMap<String, String>,
220 is_correlated: bool,
221 ) -> Vec<HashMap<String, String>> {
222 let seed = if is_correlated {
224 outer.clone()
225 } else {
226 HashMap::new()
227 };
228
229 let mut current: Vec<HashMap<String, String>> = vec![seed];
230
231 for pattern in &query.where_clause {
232 let mut next: Vec<HashMap<String, String>> = Vec::new();
233 for binding in ¤t {
234 let s = Self::resolve_term(&pattern.s, binding, outer);
236 let p = Self::resolve_term(&pattern.p, binding, outer);
237 let o = Self::resolve_term(&pattern.o, binding, outer);
238
239 let mut new_binding = binding.clone();
242
243 if let Some(var) = pattern.s.strip_prefix('?') {
245 if !new_binding.contains_key(var) {
246 if let Some(val) = &s {
247 new_binding.insert(var.to_string(), val.clone());
248 }
249 }
250 }
251 if let Some(var) = pattern.p.strip_prefix('?') {
252 if !new_binding.contains_key(var) {
253 if let Some(val) = &p {
254 new_binding.insert(var.to_string(), val.clone());
255 }
256 }
257 }
258 if let Some(var) = pattern.o.strip_prefix('?') {
259 if !new_binding.contains_key(var) {
260 if let Some(val) = &o {
261 new_binding.insert(var.to_string(), val.clone());
262 }
263 }
264 }
265
266 next.push(new_binding);
269 }
270 current = next;
271 }
272
273 current
275 }
276
277 fn resolve_term(
279 term: &str,
280 binding: &HashMap<String, String>,
281 outer: &HashMap<String, String>,
282 ) -> Option<String> {
283 if let Some(var) = term.strip_prefix('?') {
284 binding.get(var).or_else(|| outer.get(var)).cloned()
285 } else {
286 Some(term.to_string())
287 }
288 }
289
290 fn apply_filters(&self, filters: &[String], binding: &HashMap<String, String>) -> bool {
293 for filter in filters {
294 if !Self::evaluate_filter(filter, binding) {
295 return false;
296 }
297 }
298 true
299 }
300
301 fn evaluate_filter(filter: &str, binding: &HashMap<String, String>) -> bool {
304 let filter = filter.trim();
305
306 if let Some(pos) = filter.find(" != ") {
308 let lhs = filter[..pos].trim();
309 let rhs = filter[pos + 4..].trim();
310 let lval = Self::resolve_filter_term(lhs, binding);
311 let rval = Self::resolve_filter_term(rhs, binding);
312 return lval != rval;
313 }
314 if let Some(pos) = filter.find(" = ") {
315 let lhs = filter[..pos].trim();
316 let rhs = filter[pos + 3..].trim();
317 let lval = Self::resolve_filter_term(lhs, binding);
318 let rval = Self::resolve_filter_term(rhs, binding);
319 return lval == rval;
320 }
321
322 true
324 }
325
326 fn resolve_filter_term(term: &str, binding: &HashMap<String, String>) -> Option<String> {
327 if let Some(var) = term.strip_prefix('?') {
328 binding.get(var).cloned()
329 } else if (term.starts_with('\'') && term.ends_with('\''))
330 || (term.starts_with('"') && term.ends_with('"'))
331 {
332 Some(term[1..term.len() - 1].to_string())
333 } else {
334 Some(term.to_string())
335 }
336 }
337
338 pub fn project(
341 bindings: Vec<HashMap<String, String>>,
342 vars: &[String],
343 ) -> Vec<HashMap<String, String>> {
344 if vars.is_empty() {
345 return bindings;
346 }
347 bindings
348 .into_iter()
349 .map(|b| {
350 vars.iter()
351 .filter_map(|v| b.get(v).map(|val| (v.clone(), val.clone())))
352 .collect()
353 })
354 .collect()
355 }
356
357 pub fn deduplicate(bindings: Vec<HashMap<String, String>>) -> Vec<HashMap<String, String>> {
359 let mut seen: HashSet<Vec<(String, String)>> = HashSet::new();
360 let mut result = Vec::new();
361 for binding in bindings {
362 let mut sorted: Vec<(String, String)> = binding.into_iter().collect();
363 sorted.sort_by(|a, b| a.0.cmp(&b.0));
364 if seen.insert(sorted.clone()) {
365 result.push(sorted.into_iter().collect());
366 }
367 }
368 result
369 }
370
371 pub fn apply_limit_offset(
373 bindings: Vec<HashMap<String, String>>,
374 limit: Option<usize>,
375 offset: Option<usize>,
376 ) -> Vec<HashMap<String, String>> {
377 let start = offset.unwrap_or(0);
378 let sliced: Vec<_> = bindings.into_iter().skip(start).collect();
379 match limit {
380 Some(n) => sliced.into_iter().take(n).collect(),
381 None => sliced,
382 }
383 }
384
385 pub fn variables_used(query: &SelectQuery) -> Vec<String> {
387 let mut vars: HashSet<String> = HashSet::new();
388 for pattern in &query.where_clause {
389 for v in pattern.variables() {
390 vars.insert(v);
391 }
392 }
393 let mut result: Vec<String> = vars.into_iter().collect();
394 result.sort();
395 result
396 }
397
398 pub fn is_correlated(subquery: &Subquery) -> bool {
400 if subquery.outer_vars.is_empty() {
401 return false;
402 }
403 let used = Self::variables_used(&subquery.inner);
404 let outer_set: HashSet<_> = subquery.outer_vars.iter().cloned().collect();
405 used.iter().any(|v| outer_set.contains(v))
406 }
407}
408
409impl Default for SubqueryExecutor {
410 fn default() -> Self {
411 Self::new()
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418
419 fn make_binding(pairs: &[(&str, &str)]) -> HashMap<String, String> {
420 pairs
421 .iter()
422 .map(|(k, v)| (k.to_string(), v.to_string()))
423 .collect()
424 }
425
426 #[test]
429 fn test_executor_new() {
430 let _exec = SubqueryExecutor::new();
431 }
432
433 #[test]
434 fn test_executor_default() {
435 let _exec = SubqueryExecutor;
436 }
437
438 #[test]
439 fn test_simple_select_query_builder() {
440 let q = SelectQuery::new()
441 .select(&["x", "y"])
442 .where_triple("?x", "<p>", "?y")
443 .limit(10)
444 .offset(2)
445 .distinct();
446 assert_eq!(q.select_vars, vec!["x", "y"]);
447 assert_eq!(q.limit, Some(10));
448 assert_eq!(q.offset, Some(2));
449 assert!(q.distinct);
450 assert_eq!(q.where_clause.len(), 1);
451 }
452
453 #[test]
454 fn test_select_query_default() {
455 let q = SelectQuery::default();
456 assert!(q.select_vars.is_empty());
457 assert!(!q.distinct);
458 assert!(q.limit.is_none());
459 assert!(q.offset.is_none());
460 }
461
462 #[test]
463 fn test_triple_pattern_variables() {
464 let p = TriplePattern::new("?s", "<rdf:type>", "?o");
465 let vars = p.variables();
466 assert!(vars.contains(&"s".to_string()));
467 assert!(vars.contains(&"o".to_string()));
468 assert!(!vars.contains(&"rdf:type".to_string()));
469 }
470
471 #[test]
472 fn test_triple_pattern_no_variables() {
473 let p = TriplePattern::new("<s>", "<p>", "<o>");
474 assert!(p.variables().is_empty());
475 }
476
477 #[test]
478 fn test_project_reduces_vars() {
479 let bindings = vec![
480 make_binding(&[("x", "1"), ("y", "2"), ("z", "3")]),
481 make_binding(&[("x", "4"), ("y", "5"), ("z", "6")]),
482 ];
483 let projected = SubqueryExecutor::project(bindings, &["x".to_string(), "z".to_string()]);
484 assert_eq!(projected.len(), 2);
485 assert!(projected[0].contains_key("x"));
486 assert!(projected[0].contains_key("z"));
487 assert!(!projected[0].contains_key("y"));
488 }
489
490 #[test]
491 fn test_project_empty_vars_keeps_all() {
492 let bindings = vec![make_binding(&[("x", "1"), ("y", "2")])];
493 let projected = SubqueryExecutor::project(bindings, &[]);
494 assert_eq!(projected[0].len(), 2);
495 }
496
497 #[test]
498 fn test_project_missing_var_excluded() {
499 let bindings = vec![make_binding(&[("x", "1")])];
500 let projected =
501 SubqueryExecutor::project(bindings, &["x".to_string(), "missing".to_string()]);
502 assert_eq!(projected[0].len(), 1);
503 assert!(projected[0].contains_key("x"));
504 }
505
506 #[test]
507 fn test_deduplicate_removes_dupes() {
508 let bindings = vec![
509 make_binding(&[("x", "1"), ("y", "2")]),
510 make_binding(&[("x", "1"), ("y", "2")]),
511 make_binding(&[("x", "3"), ("y", "4")]),
512 ];
513 let deduped = SubqueryExecutor::deduplicate(bindings);
514 assert_eq!(deduped.len(), 2);
515 }
516
517 #[test]
518 fn test_deduplicate_empty() {
519 let result = SubqueryExecutor::deduplicate(vec![]);
520 assert!(result.is_empty());
521 }
522
523 #[test]
524 fn test_deduplicate_no_duplicates_unchanged() {
525 let bindings = vec![make_binding(&[("x", "1")]), make_binding(&[("x", "2")])];
526 let deduped = SubqueryExecutor::deduplicate(bindings);
527 assert_eq!(deduped.len(), 2);
528 }
529
530 #[test]
533 fn test_apply_limit_only() {
534 let bindings: Vec<_> = (0..10)
535 .map(|i| make_binding(&[("x", &i.to_string())]))
536 .collect();
537 let result = SubqueryExecutor::apply_limit_offset(bindings, Some(3), None);
538 assert_eq!(result.len(), 3);
539 }
540
541 #[test]
542 fn test_apply_offset_only() {
543 let bindings: Vec<_> = (0..5)
544 .map(|i| make_binding(&[("x", &i.to_string())]))
545 .collect();
546 let result = SubqueryExecutor::apply_limit_offset(bindings, None, Some(2));
547 assert_eq!(result.len(), 3);
548 }
549
550 #[test]
551 fn test_apply_limit_and_offset() {
552 let bindings: Vec<_> = (0..10)
553 .map(|i| make_binding(&[("x", &i.to_string())]))
554 .collect();
555 let result = SubqueryExecutor::apply_limit_offset(bindings, Some(3), Some(2));
556 assert_eq!(result.len(), 3);
557 }
558
559 #[test]
560 fn test_apply_offset_past_end_returns_empty() {
561 let bindings: Vec<_> = (0..5)
562 .map(|i| make_binding(&[("x", &i.to_string())]))
563 .collect();
564 let result = SubqueryExecutor::apply_limit_offset(bindings, None, Some(100));
565 assert!(result.is_empty());
566 }
567
568 #[test]
569 fn test_apply_limit_zero_returns_empty() {
570 let bindings = vec![make_binding(&[("x", "1")])];
571 let result = SubqueryExecutor::apply_limit_offset(bindings, Some(0), None);
572 assert!(result.is_empty());
573 }
574
575 #[test]
576 fn test_apply_no_limit_no_offset_unchanged() {
577 let bindings: Vec<_> = (0..4)
578 .map(|i| make_binding(&[("x", &i.to_string())]))
579 .collect();
580 let result = SubqueryExecutor::apply_limit_offset(bindings, None, None);
581 assert_eq!(result.len(), 4);
582 }
583
584 #[test]
587 fn test_variables_used_basic() {
588 let q = SelectQuery::new()
589 .where_triple("?s", "<rdf:type>", "?type")
590 .where_triple("?s", "<name>", "?name");
591 let vars = SubqueryExecutor::variables_used(&q);
592 assert!(vars.contains(&"s".to_string()));
593 assert!(vars.contains(&"type".to_string()));
594 assert!(vars.contains(&"name".to_string()));
595 }
596
597 #[test]
598 fn test_variables_used_no_vars() {
599 let q = SelectQuery::new().where_triple("<s>", "<p>", "<o>");
600 let vars = SubqueryExecutor::variables_used(&q);
601 assert!(vars.is_empty());
602 }
603
604 #[test]
605 fn test_variables_used_sorted() {
606 let q = SelectQuery::new()
607 .where_triple("?z", "<p>", "?a")
608 .where_triple("?m", "<q>", "?b");
609 let vars = SubqueryExecutor::variables_used(&q);
610 let mut sorted = vars.clone();
611 sorted.sort();
612 assert_eq!(vars, sorted);
613 }
614
615 #[test]
618 fn test_is_correlated_true() {
619 let inner = SelectQuery::new()
620 .select(&["y"])
621 .where_triple("?x", "<p>", "?y"); let sq = Subquery::new(inner, vec!["x".to_string()]);
623 assert!(SubqueryExecutor::is_correlated(&sq));
624 }
625
626 #[test]
627 fn test_is_correlated_false_no_outer_vars() {
628 let inner = SelectQuery::new()
629 .select(&["y"])
630 .where_triple("?a", "<p>", "?y");
631 let sq = Subquery::new(inner, vec![]);
632 assert!(!SubqueryExecutor::is_correlated(&sq));
633 }
634
635 #[test]
636 fn test_is_correlated_false_outer_var_not_used() {
637 let inner = SelectQuery::new()
638 .select(&["y"])
639 .where_triple("?a", "<p>", "?y");
640 let sq = Subquery::new(inner, vec!["x".to_string()]);
641 assert!(!SubqueryExecutor::is_correlated(&sq));
642 }
643
644 #[test]
647 fn test_execute_returns_bindings() {
648 let inner = SelectQuery::new()
649 .select(&["x"])
650 .where_triple("?x", "<rdf:type>", "<Person>");
651 let sq = Subquery::new(inner, vec![]);
652 let outer = vec![make_binding(&[("x", "Alice")])];
653 let executor = SubqueryExecutor::new();
654 let result = executor.execute(&sq, &outer);
655 assert!(result.bindings.len() <= outer.len() * 10 + 1);
657 }
658
659 #[test]
660 fn test_execute_empty_outer_bindings() {
661 let inner = SelectQuery::new()
662 .select(&["x"])
663 .where_triple("?x", "<p>", "<o>");
664 let sq = Subquery::new(inner, vec![]);
665 let executor = SubqueryExecutor::new();
666 let result = executor.execute(&sq, &[]);
667 assert!(!result.bindings.is_empty());
669 }
670
671 #[test]
672 fn test_execute_projects_select_vars() {
673 let inner = SelectQuery::new()
674 .select(&["x"])
675 .where_triple("?x", "<p>", "?y");
676 let sq = Subquery::new(inner, vec![]);
677 let outer = vec![make_binding(&[("x", "Alice"), ("y", "val")])];
678 let executor = SubqueryExecutor::new();
679 let result = executor.execute(&sq, &outer);
680 for binding in &result.bindings {
681 assert!(!binding.contains_key("y"), "y should be projected away");
682 }
683 }
684
685 #[test]
686 fn test_execute_distinct_true_removes_dupes() {
687 let inner = SelectQuery::new()
688 .select(&["x"])
689 .where_triple("?x", "<p>", "<o>")
690 .distinct();
691 let sq = Subquery::new(inner, vec![]);
692 let outer = vec![
694 make_binding(&[("x", "Alice")]),
695 make_binding(&[("x", "Alice")]),
696 ];
697 let executor = SubqueryExecutor::new();
698 let result = executor.execute(&sq, &outer);
699 let strings: Vec<_> = result
701 .bindings
702 .iter()
703 .map(|b| {
704 let mut v: Vec<_> = b.iter().collect();
705 v.sort();
706 format!("{v:?}")
707 })
708 .collect();
709 let unique_count = strings.iter().collect::<HashSet<_>>().len();
710 assert_eq!(strings.len(), unique_count);
711 }
712
713 #[test]
714 fn test_execute_limit_applied() {
715 let inner = SelectQuery::new()
716 .select(&["x"])
717 .where_triple("?x", "<p>", "<o>")
718 .limit(1);
719 let sq = Subquery::new(inner, vec![]);
720 let outer: Vec<_> = (0..5)
721 .map(|i| make_binding(&[("x", &i.to_string())]))
722 .collect();
723 let executor = SubqueryExecutor::new();
724 let result = executor.execute(&sq, &outer);
725 assert!(result.bindings.len() <= 1);
726 }
727
728 #[test]
729 fn test_execute_offset_applied() {
730 let inner = SelectQuery::new()
731 .select(&["x"])
732 .where_triple("?x", "<p>", "<o>")
733 .offset(2);
734 let sq = Subquery::new(inner, vec![]);
735 let outer: Vec<_> = (0..4)
736 .map(|i| make_binding(&[("x", &i.to_string())]))
737 .collect();
738 let executor = SubqueryExecutor::new();
739 let result = executor.execute(&sq, &outer);
740 assert!(result.bindings.len() <= 2);
741 }
742
743 #[test]
744 fn test_execute_correlated_subquery() {
745 let inner = SelectQuery::new()
746 .select(&["y"])
747 .where_triple("?x", "<knows>", "?y"); let sq = Subquery::new(inner, vec!["x".to_string()]);
749 assert!(SubqueryExecutor::is_correlated(&sq));
750 let outer = vec![make_binding(&[("x", "Alice")])];
751 let executor = SubqueryExecutor::new();
752 let result = executor.execute(&sq, &outer);
753 assert!(result.bindings.len() <= 100);
755 }
756
757 #[test]
758 fn test_execute_uncorrelated_subquery() {
759 let inner = SelectQuery::new()
760 .select(&["y"])
761 .where_triple("?a", "<p>", "?y");
762 let sq = Subquery::new(inner, vec!["x".to_string()]);
763 assert!(!SubqueryExecutor::is_correlated(&sq));
764 let outer = vec![make_binding(&[("x", "Alice")])];
765 let executor = SubqueryExecutor::new();
766 let result = executor.execute(&sq, &outer);
767 assert!(result.bindings.len() <= 100);
768 }
769
770 #[test]
771 fn test_execute_nested_distinct_offset_limit() {
772 let inner = SelectQuery::new()
773 .select(&["x"])
774 .where_triple("?x", "<p>", "<o>")
775 .distinct()
776 .offset(1)
777 .limit(2);
778 let sq = Subquery::new(inner, vec![]);
779 let outer: Vec<_> = (0..5)
780 .map(|i| make_binding(&[("x", &i.to_string())]))
781 .collect();
782 let executor = SubqueryExecutor::new();
783 let result = executor.execute(&sq, &outer);
784 assert!(result.bindings.len() <= 2);
785 }
786
787 #[test]
788 fn test_subquery_result_var_count() {
789 let bindings = vec![make_binding(&[("x", "1"), ("y", "2")])];
790 let res = SubqueryResult::new(bindings);
791 assert_eq!(res.var_count, 2);
792 }
793
794 #[test]
795 fn test_subquery_result_empty_var_count() {
796 let res = SubqueryResult::new(vec![]);
797 assert_eq!(res.var_count, 0);
798 }
799
800 #[test]
801 fn test_triple_pattern_new() {
802 let p = TriplePattern::new("?s", "?p", "?o");
803 assert_eq!(p.s, "?s");
804 assert_eq!(p.p, "?p");
805 assert_eq!(p.o, "?o");
806 }
807
808 #[test]
809 fn test_select_query_filter() {
810 let q = SelectQuery::new()
811 .where_triple("?x", "<p>", "?y")
812 .filter("?x = 'Alice'");
813 assert_eq!(q.filters.len(), 1);
814 assert_eq!(q.filters[0], "?x = 'Alice'");
815 }
816
817 #[test]
818 fn test_project_single_var() {
819 let bindings = vec![make_binding(&[("a", "1"), ("b", "2"), ("c", "3")])];
820 let projected = SubqueryExecutor::project(bindings, &["b".to_string()]);
821 assert_eq!(projected[0].len(), 1);
822 assert_eq!(projected[0]["b"], "2");
823 }
824
825 #[test]
826 fn test_deduplicate_single_element() {
827 let bindings = vec![make_binding(&[("x", "1")])];
828 let result = SubqueryExecutor::deduplicate(bindings);
829 assert_eq!(result.len(), 1);
830 }
831
832 #[test]
833 fn test_apply_limit_larger_than_results() {
834 let bindings = vec![make_binding(&[("x", "1")]), make_binding(&[("x", "2")])];
835 let result = SubqueryExecutor::apply_limit_offset(bindings, Some(100), None);
836 assert_eq!(result.len(), 2);
837 }
838
839 #[test]
840 fn test_is_correlated_multiple_outer_vars() {
841 let inner = SelectQuery::new()
842 .where_triple("?a", "<p>", "?c")
843 .where_triple("?b", "<q>", "?d");
844 let sq = Subquery::new(inner, vec!["a".to_string(), "b".to_string()]);
846 assert!(SubqueryExecutor::is_correlated(&sq));
847 }
848
849 #[test]
850 fn test_execute_with_filter_equality() {
851 let inner = SelectQuery::new()
852 .select(&["x"])
853 .where_triple("?x", "<p>", "<o>")
854 .filter("?x = 'Alice'");
855 let sq = Subquery::new(inner, vec![]);
856 let outer = vec![
857 make_binding(&[("x", "Alice")]),
858 make_binding(&[("x", "Bob")]),
859 ];
860 let executor = SubqueryExecutor::new();
861 let result = executor.execute(&sq, &outer);
862 for b in &result.bindings {
864 if let Some(x) = b.get("x") {
865 assert_eq!(x, "Alice");
866 }
867 }
868 }
869
870 #[test]
871 fn test_variables_used_deduplicates() {
872 let q = SelectQuery::new()
873 .where_triple("?s", "<p>", "?o")
874 .where_triple("?s", "<q>", "?z"); let vars = SubqueryExecutor::variables_used(&q);
876 let count = vars.iter().filter(|v| v.as_str() == "s").count();
877 assert_eq!(count, 1, "?s should appear only once");
878 }
879
880 #[test]
881 fn test_subquery_new_constructor() {
882 let inner = SelectQuery::new().select(&["x"]);
883 let outer_vars = vec!["y".to_string()];
884 let sq = Subquery::new(inner, outer_vars.clone());
885 assert_eq!(sq.outer_vars, outer_vars);
886 }
887
888 #[test]
889 fn test_select_query_multiple_filters() {
890 let q = SelectQuery::new()
891 .where_triple("?x", "<p>", "?y")
892 .filter("?x = 'Alice'")
893 .filter("?y = 'Bob'");
894 assert_eq!(q.filters.len(), 2);
895 }
896}