Skip to main content

oxirs_arq/
subquery.rs

1// SPARQL subquery (SELECT within SELECT) support (v1.1.0 round 11)
2//
3// Implements correlated and uncorrelated subqueries for SPARQL 1.1.
4// A subquery is a SELECT query nested inside an outer SELECT/WHERE clause.
5
6use std::collections::{HashMap, HashSet};
7
8/// A triple pattern with subject, predicate, object as strings
9#[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    /// Create a new triple pattern
18    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    /// Return all variable names referenced in this pattern (names starting with '?')
27    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/// An inner SELECT query
39#[derive(Debug, Clone)]
40pub struct SelectQuery {
41    /// Variables projected in the SELECT clause (empty = SELECT *)
42    pub select_vars: Vec<String>,
43    /// Triple patterns in the WHERE clause
44    pub where_clause: Vec<TriplePattern>,
45    /// FILTER expressions (as strings, evaluated by checking bindings)
46    pub filters: Vec<String>,
47    /// LIMIT clause
48    pub limit: Option<usize>,
49    /// OFFSET clause
50    pub offset: Option<usize>,
51    /// SELECT DISTINCT
52    pub distinct: bool,
53}
54
55impl SelectQuery {
56    /// Create an empty SELECT query
57    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    /// Add projected variables
69    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    /// Add a triple pattern to WHERE
75    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    /// Add a FILTER expression
81    pub fn filter(mut self, f: impl Into<String>) -> Self {
82        self.filters.push(f.into());
83        self
84    }
85
86    /// Set LIMIT
87    pub fn limit(mut self, n: usize) -> Self {
88        self.limit = Some(n);
89        self
90    }
91
92    /// Set OFFSET
93    pub fn offset(mut self, n: usize) -> Self {
94        self.offset = Some(n);
95        self
96    }
97
98    /// Enable DISTINCT
99    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/// A SPARQL subquery: an inner SELECT paired with the outer variable bindings it may reference
112#[derive(Debug, Clone)]
113pub struct Subquery {
114    pub inner: Box<SelectQuery>,
115    /// Variables from the outer query that this subquery may reference
116    pub outer_vars: Vec<String>,
117}
118
119impl Subquery {
120    /// Create a new subquery
121    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/// Results produced by executing a subquery
130#[derive(Debug, Clone)]
131pub struct SubqueryResult {
132    /// The result bindings (each binding is a map from var name → value)
133    pub bindings: Vec<HashMap<String, String>>,
134    /// Number of distinct variables in the result
135    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
148/// Executor for SPARQL subqueries
149pub struct SubqueryExecutor;
150
151impl SubqueryExecutor {
152    /// Create a new executor
153    pub fn new() -> Self {
154        Self
155    }
156
157    /// Execute a subquery given a set of outer bindings.
158    ///
159    /// The algorithm:
160    /// 1. For each triple pattern in the inner WHERE, generate candidate bindings
161    ///    by matching against the outer bindings (simulated evaluation).
162    /// 2. If the subquery is correlated, inject the outer binding into every candidate.
163    /// 3. Apply FILTER expressions (simple equality checks on bound variables).
164    /// 4. Project to the SELECT variables.
165    /// 5. Apply DISTINCT, OFFSET, LIMIT.
166    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        // Collect all ground (non-variable) triples from outer_bindings to form a fake triple store
175        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            // Apply filters
187            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        // Project
196        let mut projected = Self::project(all_results, &inner.select_vars);
197
198        // Deduplicate if DISTINCT
199        if inner.distinct {
200            projected = Self::deduplicate(projected);
201        }
202
203        // Apply OFFSET then LIMIT
204        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    /// Evaluate the WHERE clause triple patterns to produce candidate bindings.
215    /// This is a simulation: it does naive variable binding from patterns.
216    fn evaluate_where_clause(
217        &self,
218        query: &SelectQuery,
219        outer: &HashMap<String, String>,
220        is_correlated: bool,
221    ) -> Vec<HashMap<String, String>> {
222        // Start with one empty binding (optionally seeded with outer vars)
223        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 &current {
234                // Resolve s, p, o terms
235                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                // If all three are ground values → the pattern is "satisfied" as a ground triple
240                // Produce a binding that includes whatever was bound
241                let mut new_binding = binding.clone();
242
243                // Bind unbound variables to their resolved values
244                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                // Only emit if we made at least some progress (bound at least one new variable,
267                // or the pattern had all ground terms which means the pattern holds vacuously)
268                next.push(new_binding);
269            }
270            current = next;
271        }
272
273        // If no patterns were specified, return the seed
274        current
275    }
276
277    /// Resolve a term: if it starts with '?' try looking up in binding then outer_binding.
278    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    /// Apply simple filter expressions to a binding.
291    /// Supports: "?var = 'value'" style.
292    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    /// Evaluate a single filter expression against a binding.
302    /// Supports: "?var = 'value'", "?var != 'value'", "?a = ?b".
303    fn evaluate_filter(filter: &str, binding: &HashMap<String, String>) -> bool {
304        let filter = filter.trim();
305
306        // Try "?x = ?y" or "?x != ?y"
307        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        // Unknown filter — pass
323        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    /// Project bindings to only the specified variables.
339    /// If `vars` is empty, all variables are kept (SELECT *).
340    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    /// Remove duplicate bindings
358    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    /// Apply LIMIT and OFFSET to a binding sequence
372    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    /// Return all variable names used in the query's WHERE clause
386    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    /// Return true if the subquery references any outer variables in its WHERE clause
399    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    // ── Basic execute / project / deduplicate ──────────────────────────────
427
428    #[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    // ── apply_limit_offset ─────────────────────────────────────────────────
531
532    #[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    // ── variables_used ─────────────────────────────────────────────────────
585
586    #[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    // ── is_correlated ──────────────────────────────────────────────────────
616
617    #[test]
618    fn test_is_correlated_true() {
619        let inner = SelectQuery::new()
620            .select(&["y"])
621            .where_triple("?x", "<p>", "?y"); // ?x comes from outer
622        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    // ── execute ────────────────────────────────────────────────────────────
645
646    #[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        // Should at least run without error and return some form of result
656        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        // With no outer bindings, a single empty-seed evaluation occurs
668        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        // Two identical outer bindings → should produce deduped results
693        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        // Check for no exact duplicates
700        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"); // ?x from outer
748        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        // Result should exist (doesn't crash)
754        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        // outer_vars has "a" and "b" which appear in the WHERE
845        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        // Only Alice should survive the filter
863        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"); // ?s appears twice
875        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}