Skip to main content

reddb_server/storage/query/engine/
iterator.rs

1//! Query Result Iterators
2//!
3//! Lazy evaluation of query results through binding streams.
4//!
5//! # Design
6//!
7//! - Pull-based iteration (demand-driven)
8//! - Composable iterator wrappers
9//! - Memory-efficient streaming
10//! - Early termination support
11
12use super::binding::{Binding, Value, Var};
13use super::cancel::CancelToken;
14use std::collections::HashSet;
15use std::fmt::Debug;
16
17/// Issue #808 / 750d — cooperative cancellation check shared by every
18/// pull-based operator. Returns [`IterError::Cancelled`] the moment the
19/// stream layer raises the token, so a long-running scan / filter / join
20/// loop stops within one row of the cancel signal rather than running to
21/// completion. A `None` token (the default) never cancels, so operators
22/// built without a token behave exactly as before.
23#[inline]
24fn check_shared_cancel(token: &Option<CancelToken>) -> Result<(), IterError> {
25    if let Some(token) = token {
26        if token.is_cancelled() {
27            return Err(IterError::Cancelled);
28        }
29    }
30    Ok(())
31}
32
33/// Result from iterator operations
34pub type IterResult = Result<Option<Binding>, IterError>;
35
36/// Iterator errors
37#[derive(Debug, Clone)]
38pub enum IterError {
39    /// Execution error
40    Execution(String),
41    /// Timeout
42    Timeout,
43    /// Cancelled
44    Cancelled,
45    /// Resource exhausted
46    ResourceExhausted(String),
47}
48
49impl std::fmt::Display for IterError {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        match self {
52            IterError::Execution(msg) => write!(f, "Execution error: {}", msg),
53            IterError::Timeout => write!(f, "Query timeout"),
54            IterError::Cancelled => write!(f, "Query cancelled"),
55            IterError::ResourceExhausted(msg) => write!(f, "Resource exhausted: {}", msg),
56        }
57    }
58}
59
60impl std::error::Error for IterError {}
61
62/// Core trait for binding iterators
63pub trait BindingIterator: Debug + Send {
64    /// Get next binding
65    fn next_binding(&mut self) -> IterResult;
66
67    /// Check if there are more results (optional hint)
68    fn has_next(&self) -> bool {
69        true // Default: unknown, caller should try next()
70    }
71
72    /// Get variables produced by this iterator
73    fn vars(&self) -> Vec<Var>;
74
75    /// Cancel iteration
76    fn cancel(&mut self);
77
78    /// Reset iterator to beginning (if supported)
79    fn reset(&mut self) -> bool {
80        false // Default: not supported
81    }
82}
83
84/// Base query iterator wrapping a binding source
85#[derive(Debug)]
86pub struct QueryIterBase {
87    bindings: Vec<Binding>,
88    index: usize,
89    vars: Vec<Var>,
90    cancelled: bool,
91    /// Issue #808 / 750d — shared cancel token observed on every pull so a
92    /// scan over a large binding set stops promptly when the stream is
93    /// cancelled. `None` (the default) preserves the legacy behaviour.
94    cancel: Option<CancelToken>,
95}
96
97impl QueryIterBase {
98    /// Create from binding list
99    pub fn new(bindings: Vec<Binding>) -> Self {
100        let vars = if let Some(first) = bindings.first() {
101            first.all_vars().into_iter().cloned().collect()
102        } else {
103            Vec::new()
104        };
105
106        Self {
107            bindings,
108            index: 0,
109            vars,
110            cancelled: false,
111            cancel: None,
112        }
113    }
114
115    /// Create empty iterator
116    pub fn empty() -> Self {
117        Self {
118            bindings: Vec::new(),
119            index: 0,
120            vars: Vec::new(),
121            cancelled: false,
122            cancel: None,
123        }
124    }
125
126    /// Create single-result iterator
127    pub fn single(binding: Binding) -> Self {
128        let vars = binding.all_vars().into_iter().cloned().collect();
129        Self {
130            bindings: vec![binding],
131            index: 0,
132            vars,
133            cancelled: false,
134            cancel: None,
135        }
136    }
137
138    /// Issue #808 / 750d — attach a shared cancel token. The scan checks it
139    /// on every `next_binding`, so cancelling the token (from the stream
140    /// layer on disconnect or explicit cancel) halts production within one
141    /// row regardless of how many bindings remain.
142    pub fn with_cancel(mut self, token: CancelToken) -> Self {
143        self.cancel = Some(token);
144        self
145    }
146}
147
148impl BindingIterator for QueryIterBase {
149    fn next_binding(&mut self) -> IterResult {
150        if self.cancelled {
151            return Err(IterError::Cancelled);
152        }
153        check_shared_cancel(&self.cancel)?;
154
155        if self.index < self.bindings.len() {
156            // Each binding is emitted exactly once, so move it out of the
157            // backing store instead of cloning per row. `reset()` remains
158            // structurally valid (index rewinds); it just re-emits the moved
159            // slots as defaults — and it is never invoked on this iterator.
160            let binding = std::mem::take(&mut self.bindings[self.index]);
161            self.index += 1;
162            Ok(Some(binding))
163        } else {
164            Ok(None)
165        }
166    }
167
168    fn has_next(&self) -> bool {
169        !self.cancelled && self.index < self.bindings.len()
170    }
171
172    fn vars(&self) -> Vec<Var> {
173        self.vars.clone()
174    }
175
176    fn cancel(&mut self) {
177        self.cancelled = true;
178    }
179
180    fn reset(&mut self) -> bool {
181        self.index = 0;
182        self.cancelled = false;
183        true
184    }
185}
186
187/// Filter iterator - applies predicate to upstream
188pub struct QueryIterFilter {
189    upstream: Box<dyn BindingIterator>,
190    predicate: Box<dyn Fn(&Binding) -> bool + Send + Sync>,
191    cancelled: bool,
192    /// Issue #808 / 750d — shared cancel token. A highly selective filter
193    /// can pull many upstream rows before yielding one; the token is
194    /// re-checked on every upstream pull so the rejection loop cannot
195    /// outrun the cancel signal.
196    cancel: Option<CancelToken>,
197}
198
199impl std::fmt::Debug for QueryIterFilter {
200    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201        f.debug_struct("QueryIterFilter")
202            .field("upstream", &self.upstream)
203            .field("predicate", &"<filter fn>")
204            .field("cancelled", &self.cancelled)
205            .finish()
206    }
207}
208
209impl QueryIterFilter {
210    /// Create filter iterator
211    pub fn new<F>(upstream: Box<dyn BindingIterator>, predicate: F) -> Self
212    where
213        F: Fn(&Binding) -> bool + Send + Sync + 'static,
214    {
215        Self {
216            upstream,
217            predicate: Box::new(predicate),
218            cancelled: false,
219            cancel: None,
220        }
221    }
222
223    /// Issue #808 / 750d — attach a shared cancel token (see [`QueryIterBase::with_cancel`]).
224    pub fn with_cancel(mut self, token: CancelToken) -> Self {
225        self.cancel = Some(token);
226        self
227    }
228}
229
230impl BindingIterator for QueryIterFilter {
231    fn next_binding(&mut self) -> IterResult {
232        if self.cancelled {
233            return Err(IterError::Cancelled);
234        }
235
236        loop {
237            check_shared_cancel(&self.cancel)?;
238            match self.upstream.next_binding()? {
239                Some(binding) => {
240                    if (self.predicate)(&binding) {
241                        return Ok(Some(binding));
242                    }
243                    // Continue to next
244                }
245                None => return Ok(None),
246            }
247        }
248    }
249
250    fn vars(&self) -> Vec<Var> {
251        self.upstream.vars()
252    }
253
254    fn cancel(&mut self) {
255        self.cancelled = true;
256        self.upstream.cancel();
257    }
258}
259
260/// Join iterator - nested loop join
261pub struct QueryIterJoin {
262    left: Box<dyn BindingIterator>,
263    right_factory: Box<dyn Fn() -> Box<dyn BindingIterator> + Send + Sync>,
264    current_left: Option<Binding>,
265    current_right: Option<Box<dyn BindingIterator>>,
266    vars: Vec<Var>,
267    cancelled: bool,
268    /// Issue #808 / 750d — shared cancel token. A nested-loop join is the
269    /// worst case for unbounded work (|left| × |right|); the token is
270    /// re-checked on every iteration of the merge loop so a cancel stops
271    /// the join without draining the cross product.
272    cancel: Option<CancelToken>,
273}
274
275impl std::fmt::Debug for QueryIterJoin {
276    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277        f.debug_struct("QueryIterJoin")
278            .field("left", &self.left)
279            .field("right_factory", &"<factory fn>")
280            .field("current_left", &self.current_left)
281            .field("current_right", &self.current_right)
282            .field("vars", &self.vars)
283            .field("cancelled", &self.cancelled)
284            .finish()
285    }
286}
287
288impl QueryIterJoin {
289    /// Create join iterator
290    pub fn new<F>(left: Box<dyn BindingIterator>, right_factory: F, right_vars: Vec<Var>) -> Self
291    where
292        F: Fn() -> Box<dyn BindingIterator> + Send + Sync + 'static,
293    {
294        let mut vars = left.vars();
295        for v in right_vars {
296            if !vars.contains(&v) {
297                vars.push(v);
298            }
299        }
300
301        Self {
302            left,
303            right_factory: Box::new(right_factory),
304            current_left: None,
305            current_right: None,
306            vars,
307            cancelled: false,
308            cancel: None,
309        }
310    }
311
312    /// Issue #808 / 750d — attach a shared cancel token (see [`QueryIterBase::with_cancel`]).
313    pub fn with_cancel(mut self, token: CancelToken) -> Self {
314        self.cancel = Some(token);
315        self
316    }
317}
318
319impl BindingIterator for QueryIterJoin {
320    fn next_binding(&mut self) -> IterResult {
321        if self.cancelled {
322            return Err(IterError::Cancelled);
323        }
324
325        loop {
326            check_shared_cancel(&self.cancel)?;
327            // Try to get next from current right
328            if let Some(ref mut right) = self.current_right {
329                if let Some(right_binding) = right.next_binding()? {
330                    // Merge with current left
331                    if let Some(ref left_binding) = self.current_left {
332                        if let Some(merged) = left_binding.merge(&right_binding) {
333                            return Ok(Some(merged));
334                        }
335                        // Conflict, continue to next right
336                        continue;
337                    }
338                }
339            }
340
341            // Need new left binding
342            match self.left.next_binding()? {
343                Some(left_binding) => {
344                    self.current_left = Some(left_binding);
345                    self.current_right = Some((self.right_factory)());
346                }
347                None => return Ok(None),
348            }
349        }
350    }
351
352    fn vars(&self) -> Vec<Var> {
353        self.vars.clone()
354    }
355
356    fn cancel(&mut self) {
357        self.cancelled = true;
358        self.left.cancel();
359        if let Some(ref mut right) = self.current_right {
360            right.cancel();
361        }
362    }
363}
364
365/// Union iterator - concatenates multiple iterators
366#[derive(Debug)]
367pub struct QueryIterUnion {
368    iterators: Vec<Box<dyn BindingIterator>>,
369    current_index: usize,
370    vars: Vec<Var>,
371    cancelled: bool,
372}
373
374impl QueryIterUnion {
375    /// Create union iterator
376    pub fn new(iterators: Vec<Box<dyn BindingIterator>>) -> Self {
377        let mut vars = Vec::new();
378        for iter in &iterators {
379            for v in iter.vars() {
380                if !vars.contains(&v) {
381                    vars.push(v);
382                }
383            }
384        }
385
386        Self {
387            iterators,
388            current_index: 0,
389            vars,
390            cancelled: false,
391        }
392    }
393}
394
395impl BindingIterator for QueryIterUnion {
396    fn next_binding(&mut self) -> IterResult {
397        if self.cancelled {
398            return Err(IterError::Cancelled);
399        }
400
401        while self.current_index < self.iterators.len() {
402            match self.iterators[self.current_index].next_binding()? {
403                Some(binding) => return Ok(Some(binding)),
404                None => {
405                    self.current_index += 1;
406                }
407            }
408        }
409
410        Ok(None)
411    }
412
413    fn vars(&self) -> Vec<Var> {
414        self.vars.clone()
415    }
416
417    fn cancel(&mut self) {
418        self.cancelled = true;
419        for iter in &mut self.iterators {
420            iter.cancel();
421        }
422    }
423}
424
425/// Project iterator - selects subset of variables
426#[derive(Debug)]
427pub struct QueryIterProject {
428    upstream: Box<dyn BindingIterator>,
429    project_vars: Vec<Var>,
430    cancelled: bool,
431}
432
433impl QueryIterProject {
434    /// Create project iterator
435    pub fn new(upstream: Box<dyn BindingIterator>, vars: Vec<Var>) -> Self {
436        Self {
437            upstream,
438            project_vars: vars,
439            cancelled: false,
440        }
441    }
442}
443
444impl BindingIterator for QueryIterProject {
445    fn next_binding(&mut self) -> IterResult {
446        if self.cancelled {
447            return Err(IterError::Cancelled);
448        }
449
450        match self.upstream.next_binding()? {
451            Some(binding) => Ok(Some(binding.project(&self.project_vars))),
452            None => Ok(None),
453        }
454    }
455
456    fn vars(&self) -> Vec<Var> {
457        self.project_vars.clone()
458    }
459
460    fn cancel(&mut self) {
461        self.cancelled = true;
462        self.upstream.cancel();
463    }
464}
465
466/// Slice iterator - limit and offset
467#[derive(Debug)]
468pub struct QueryIterSlice {
469    upstream: Box<dyn BindingIterator>,
470    offset: u64,
471    limit: Option<u64>,
472    skipped: u64,
473    returned: u64,
474    cancelled: bool,
475}
476
477impl QueryIterSlice {
478    /// Create slice iterator
479    pub fn new(upstream: Box<dyn BindingIterator>, offset: u64, limit: Option<u64>) -> Self {
480        Self {
481            upstream,
482            offset,
483            limit,
484            skipped: 0,
485            returned: 0,
486            cancelled: false,
487        }
488    }
489
490    /// Create limit-only iterator
491    pub fn limit(upstream: Box<dyn BindingIterator>, limit: u64) -> Self {
492        Self::new(upstream, 0, Some(limit))
493    }
494
495    /// Create offset-only iterator
496    pub fn offset(upstream: Box<dyn BindingIterator>, offset: u64) -> Self {
497        Self::new(upstream, offset, None)
498    }
499}
500
501impl BindingIterator for QueryIterSlice {
502    fn next_binding(&mut self) -> IterResult {
503        if self.cancelled {
504            return Err(IterError::Cancelled);
505        }
506
507        // Check limit
508        if let Some(limit) = self.limit {
509            if self.returned >= limit {
510                return Ok(None);
511            }
512        }
513
514        // Skip offset
515        while self.skipped < self.offset {
516            match self.upstream.next_binding()? {
517                Some(_) => {
518                    self.skipped += 1;
519                }
520                None => return Ok(None),
521            }
522        }
523
524        // Return result
525        match self.upstream.next_binding()? {
526            Some(binding) => {
527                self.returned += 1;
528                Ok(Some(binding))
529            }
530            None => Ok(None),
531        }
532    }
533
534    fn vars(&self) -> Vec<Var> {
535        self.upstream.vars()
536    }
537
538    fn cancel(&mut self) {
539        self.cancelled = true;
540        self.upstream.cancel();
541    }
542}
543
544/// Sort iterator - orders results
545#[derive(Debug)]
546pub struct QueryIterSort {
547    upstream: Box<dyn BindingIterator>,
548    comparators: Vec<SortKey>,
549    sorted: Option<Vec<Binding>>,
550    index: usize,
551    cancelled: bool,
552}
553
554/// Sort key specification
555#[derive(Debug, Clone)]
556pub struct SortKey {
557    /// Variable to sort by
558    pub var: Var,
559    /// Ascending order
560    pub ascending: bool,
561}
562
563impl SortKey {
564    /// Create ascending sort key
565    pub fn asc(var: Var) -> Self {
566        Self {
567            var,
568            ascending: true,
569        }
570    }
571
572    /// Create descending sort key
573    pub fn desc(var: Var) -> Self {
574        Self {
575            var,
576            ascending: false,
577        }
578    }
579}
580
581impl QueryIterSort {
582    /// Create sort iterator
583    pub fn new(upstream: Box<dyn BindingIterator>, comparators: Vec<SortKey>) -> Self {
584        Self {
585            upstream,
586            comparators,
587            sorted: None,
588            index: 0,
589            cancelled: false,
590        }
591    }
592
593    /// Materialize and sort all results
594    fn materialize(&mut self) -> Result<(), IterError> {
595        if self.sorted.is_some() {
596            return Ok(());
597        }
598
599        let mut bindings = Vec::new();
600        while let Some(b) = self.upstream.next_binding()? {
601            bindings.push(b);
602        }
603
604        // Sort by comparators
605        let comparators = self.comparators.clone();
606        bindings.sort_by(|a, b| {
607            for key in &comparators {
608                let a_val = a.get(&key.var);
609                let b_val = b.get(&key.var);
610
611                let ordering = compare_values(a_val, b_val);
612                if ordering != std::cmp::Ordering::Equal {
613                    return if key.ascending {
614                        ordering
615                    } else {
616                        ordering.reverse()
617                    };
618                }
619            }
620            std::cmp::Ordering::Equal
621        });
622
623        self.sorted = Some(bindings);
624        Ok(())
625    }
626}
627
628impl BindingIterator for QueryIterSort {
629    fn next_binding(&mut self) -> IterResult {
630        if self.cancelled {
631            return Err(IterError::Cancelled);
632        }
633
634        self.materialize()?;
635
636        if let Some(ref sorted) = self.sorted {
637            if self.index < sorted.len() {
638                let binding = sorted[self.index].clone();
639                self.index += 1;
640                return Ok(Some(binding));
641            }
642        }
643
644        Ok(None)
645    }
646
647    fn vars(&self) -> Vec<Var> {
648        self.upstream.vars()
649    }
650
651    fn cancel(&mut self) {
652        self.cancelled = true;
653        self.upstream.cancel();
654    }
655}
656
657/// Compare two optional values
658fn compare_values(a: Option<&Value>, b: Option<&Value>) -> std::cmp::Ordering {
659    match (a, b) {
660        (None, None) => std::cmp::Ordering::Equal,
661        (None, Some(_)) => std::cmp::Ordering::Less,
662        (Some(_), None) => std::cmp::Ordering::Greater,
663        (Some(a_val), Some(b_val)) => compare_value(a_val, b_val),
664    }
665}
666
667/// Compare two values
668fn compare_value(a: &Value, b: &Value) -> std::cmp::Ordering {
669    match (a, b) {
670        (Value::Integer(a), Value::Integer(b)) => a.cmp(b),
671        (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal),
672        (Value::String(a), Value::String(b)) => a.cmp(b),
673        (Value::Boolean(a), Value::Boolean(b)) => a.cmp(b),
674        (Value::Node(a), Value::Node(b)) => a.cmp(b),
675        (Value::Edge(a), Value::Edge(b)) => a.cmp(b),
676        (Value::Uri(a), Value::Uri(b)) => a.cmp(b),
677        (Value::Null, Value::Null) => std::cmp::Ordering::Equal,
678        // Cross-type comparison: types differ, so we return a consistent ordering
679        _ => {
680            let type_order = |v: &Value| -> u8 {
681                match v {
682                    Value::Null => 0,
683                    Value::Boolean(_) => 1,
684                    Value::Integer(_) => 2,
685                    Value::Float(_) => 3,
686                    Value::String(_) => 4,
687                    Value::Node(_) => 5,
688                    Value::Edge(_) => 6,
689                    Value::Uri(_) => 7,
690                }
691            };
692            type_order(a).cmp(&type_order(b))
693        }
694    }
695}
696
697/// Distinct iterator - removes duplicates
698#[derive(Debug)]
699pub struct QueryIterDistinct {
700    upstream: Box<dyn BindingIterator>,
701    seen: HashSet<u64>,
702    cancelled: bool,
703}
704
705impl QueryIterDistinct {
706    /// Create distinct iterator
707    pub fn new(upstream: Box<dyn BindingIterator>) -> Self {
708        Self {
709            upstream,
710            seen: HashSet::new(),
711            cancelled: false,
712        }
713    }
714
715    /// Hash a binding for deduplication
716    fn hash_binding(binding: &Binding) -> u64 {
717        use std::hash::{Hash, Hasher};
718        let mut hasher = std::collections::hash_map::DefaultHasher::new();
719        binding.hash(&mut hasher);
720        hasher.finish()
721    }
722}
723
724impl BindingIterator for QueryIterDistinct {
725    fn next_binding(&mut self) -> IterResult {
726        if self.cancelled {
727            return Err(IterError::Cancelled);
728        }
729
730        loop {
731            match self.upstream.next_binding()? {
732                Some(binding) => {
733                    let hash = Self::hash_binding(&binding);
734                    if self.seen.insert(hash) {
735                        return Ok(Some(binding));
736                    }
737                    // Already seen, continue
738                }
739                None => return Ok(None),
740            }
741        }
742    }
743
744    fn vars(&self) -> Vec<Var> {
745        self.upstream.vars()
746    }
747
748    fn cancel(&mut self) {
749        self.cancelled = true;
750        self.upstream.cancel();
751    }
752}
753
754/// Wrapper for boxed iterator to implement Iterator trait
755pub struct QueryIter {
756    inner: Box<dyn BindingIterator>,
757}
758
759impl QueryIter {
760    /// Create from binding iterator
761    pub fn new(inner: Box<dyn BindingIterator>) -> Self {
762        Self { inner }
763    }
764
765    /// Get variables
766    pub fn vars(&self) -> Vec<Var> {
767        self.inner.vars()
768    }
769
770    /// Cancel iteration
771    pub fn cancel(&mut self) {
772        self.inner.cancel();
773    }
774}
775
776impl Iterator for QueryIter {
777    type Item = Result<Binding, IterError>;
778
779    fn next(&mut self) -> Option<Self::Item> {
780        match self.inner.next_binding() {
781            Ok(Some(binding)) => Some(Ok(binding)),
782            Ok(None) => None,
783            Err(e) => Some(Err(e)),
784        }
785    }
786}
787
788#[cfg(test)]
789mod tests {
790    use super::*;
791    use crate::storage::query::engine::binding::BindingBuilder;
792
793    fn make_binding(x: i64) -> Binding {
794        BindingBuilder::new()
795            .add_named("x", Value::Integer(x))
796            .build()
797    }
798
799    #[test]
800    fn test_base_iterator() {
801        let bindings = vec![make_binding(1), make_binding(2), make_binding(3)];
802        let mut iter = QueryIterBase::new(bindings);
803
804        assert!(iter.has_next());
805        assert!(iter.next_binding().unwrap().is_some());
806        assert!(iter.next_binding().unwrap().is_some());
807        assert!(iter.next_binding().unwrap().is_some());
808        assert!(iter.next_binding().unwrap().is_none());
809    }
810
811    #[test]
812    fn test_filter_iterator() {
813        let bindings = vec![make_binding(1), make_binding(2), make_binding(3)];
814        let base = Box::new(QueryIterBase::new(bindings));
815
816        let mut iter = QueryIterFilter::new(base, |b| {
817            b.get(&Var::new("x"))
818                .and_then(|v| v.as_integer())
819                .map(|i| i > 1)
820                .unwrap_or(false)
821        });
822
823        // Should skip 1, return 2 and 3
824        let b1 = iter.next_binding().unwrap().unwrap();
825        assert_eq!(b1.get(&Var::new("x")), Some(&Value::Integer(2)));
826
827        let b2 = iter.next_binding().unwrap().unwrap();
828        assert_eq!(b2.get(&Var::new("x")), Some(&Value::Integer(3)));
829
830        assert!(iter.next_binding().unwrap().is_none());
831    }
832
833    #[test]
834    fn test_slice_iterator() {
835        let bindings: Vec<_> = (1..=10).map(make_binding).collect();
836        let base = Box::new(QueryIterBase::new(bindings));
837
838        // Offset 2, limit 3
839        let mut iter = QueryIterSlice::new(base, 2, Some(3));
840
841        let b1 = iter.next_binding().unwrap().unwrap();
842        assert_eq!(b1.get(&Var::new("x")), Some(&Value::Integer(3)));
843
844        let b2 = iter.next_binding().unwrap().unwrap();
845        assert_eq!(b2.get(&Var::new("x")), Some(&Value::Integer(4)));
846
847        let b3 = iter.next_binding().unwrap().unwrap();
848        assert_eq!(b3.get(&Var::new("x")), Some(&Value::Integer(5)));
849
850        assert!(iter.next_binding().unwrap().is_none());
851    }
852
853    #[test]
854    fn test_project_iterator() {
855        let binding = BindingBuilder::new()
856            .add_named("x", Value::Integer(1))
857            .add_named("y", Value::Integer(2))
858            .add_named("z", Value::Integer(3))
859            .build();
860
861        let base = Box::new(QueryIterBase::single(binding));
862        let mut iter = QueryIterProject::new(base, vec![Var::new("x"), Var::new("z")]);
863
864        let result = iter.next_binding().unwrap().unwrap();
865        assert!(result.contains(&Var::new("x")));
866        assert!(!result.contains(&Var::new("y")));
867        assert!(result.contains(&Var::new("z")));
868    }
869
870    #[test]
871    fn test_union_iterator() {
872        let iter1 = Box::new(QueryIterBase::new(vec![make_binding(1), make_binding(2)]));
873        let iter2 = Box::new(QueryIterBase::new(vec![make_binding(3), make_binding(4)]));
874
875        let mut union = QueryIterUnion::new(vec![iter1, iter2]);
876
877        let mut results = Vec::new();
878        while let Some(b) = union.next_binding().unwrap() {
879            results.push(b.get(&Var::new("x")).unwrap().as_integer().unwrap());
880        }
881
882        assert_eq!(results, vec![1, 2, 3, 4]);
883    }
884
885    #[test]
886    fn test_sort_iterator() {
887        let bindings = vec![make_binding(3), make_binding(1), make_binding(2)];
888        let base = Box::new(QueryIterBase::new(bindings));
889
890        let mut iter = QueryIterSort::new(base, vec![SortKey::asc(Var::new("x"))]);
891
892        let mut results = Vec::new();
893        while let Some(b) = iter.next_binding().unwrap() {
894            results.push(b.get(&Var::new("x")).unwrap().as_integer().unwrap());
895        }
896
897        assert_eq!(results, vec![1, 2, 3]);
898    }
899
900    #[test]
901    fn test_distinct_iterator() {
902        let bindings = vec![
903            make_binding(1),
904            make_binding(2),
905            make_binding(1),
906            make_binding(3),
907            make_binding(2),
908        ];
909        let base = Box::new(QueryIterBase::new(bindings));
910
911        let mut iter = QueryIterDistinct::new(base);
912
913        let mut results = Vec::new();
914        while let Some(b) = iter.next_binding().unwrap() {
915            results.push(b.get(&Var::new("x")).unwrap().as_integer().unwrap());
916        }
917
918        assert_eq!(results, vec![1, 2, 3]);
919    }
920
921    #[test]
922    fn test_cancel_iterator() {
923        let bindings: Vec<_> = (1..=100).map(make_binding).collect();
924        let mut iter = QueryIterBase::new(bindings);
925
926        // Read a few
927        iter.next_binding().unwrap();
928        iter.next_binding().unwrap();
929
930        // Cancel
931        iter.cancel();
932
933        // Should return cancelled error
934        assert!(matches!(iter.next_binding(), Err(IterError::Cancelled)));
935    }
936
937    #[test]
938    fn test_query_iter_wrapper() {
939        let bindings = vec![make_binding(1), make_binding(2)];
940        let base = Box::new(QueryIterBase::new(bindings));
941
942        let iter = QueryIter::new(base);
943        let results: Vec<_> = iter.collect();
944
945        assert_eq!(results.len(), 2);
946        assert!(results.iter().all(|r| r.is_ok()));
947    }
948
949    // ---- Issue #808 / 750d — cooperative shared-token cancellation. ----
950
951    /// An upstream that yields rows forever, so a downstream operator's
952    /// loop only terminates when it observes the shared cancel token. Used
953    /// to pin the latency budget for the cancel signal without depending on
954    /// a finite (and slow-to-build) materialised input.
955    #[derive(Debug)]
956    struct InfiniteScan {
957        n: i64,
958    }
959
960    impl BindingIterator for InfiniteScan {
961        fn next_binding(&mut self) -> IterResult {
962            self.n += 1;
963            Ok(Some(make_binding(self.n)))
964        }
965
966        fn vars(&self) -> Vec<Var> {
967            vec![Var::new("x")]
968        }
969
970        fn cancel(&mut self) {}
971    }
972
973    #[test]
974    fn shared_token_cancels_scan() {
975        let token = CancelToken::new();
976        let bindings: Vec<_> = (1..=1000).map(make_binding).collect();
977        let mut scan = QueryIterBase::new(bindings).with_cancel(token.clone());
978
979        assert!(scan.next_binding().unwrap().is_some());
980        token.cancel();
981        // The scan still has hundreds of rows buffered, but the shared
982        // token short-circuits the next pull.
983        assert!(matches!(scan.next_binding(), Err(IterError::Cancelled)));
984    }
985
986    #[test]
987    fn shared_token_cancels_filter_within_budget() {
988        use std::time::{Duration, Instant};
989
990        let token = CancelToken::new();
991        // Always-false predicate: the filter pulls from an infinite scan
992        // forever, never yielding. Only the shared token can stop it.
993        let upstream = Box::new(InfiniteScan { n: 0 });
994        let mut filter = QueryIterFilter::new(upstream, |_| false).with_cancel(token.clone());
995
996        let canceller = token.clone();
997        std::thread::spawn(move || {
998            std::thread::sleep(Duration::from_millis(20));
999            canceller.cancel();
1000        });
1001
1002        let start = Instant::now();
1003        let result = filter.next_binding();
1004        let elapsed = start.elapsed();
1005
1006        assert!(matches!(result, Err(IterError::Cancelled)));
1007        // Soft latency budget: the cancel must take effect well within a
1008        // second even though the filter would otherwise loop forever.
1009        assert!(
1010            elapsed < Duration::from_secs(1),
1011            "cancel took too long to take effect: {elapsed:?}"
1012        );
1013    }
1014
1015    #[test]
1016    fn shared_token_cancels_join_within_budget() {
1017        use std::time::{Duration, Instant};
1018
1019        let token = CancelToken::new();
1020        // Left side never yields a mergeable row (always-false filter over
1021        // an infinite scan), so the join's merge loop spins until the
1022        // shared token stops it.
1023        let left = Box::new(
1024            QueryIterFilter::new(Box::new(InfiniteScan { n: 0 }), |_| false)
1025                .with_cancel(token.clone()),
1026        );
1027        let mut join = QueryIterJoin::new(
1028            left,
1029            || Box::new(QueryIterBase::empty()),
1030            vec![Var::new("y")],
1031        )
1032        .with_cancel(token.clone());
1033
1034        let canceller = token.clone();
1035        std::thread::spawn(move || {
1036            std::thread::sleep(Duration::from_millis(20));
1037            canceller.cancel();
1038        });
1039
1040        let start = Instant::now();
1041        let result = join.next_binding();
1042        let elapsed = start.elapsed();
1043
1044        assert!(matches!(result, Err(IterError::Cancelled)));
1045        assert!(
1046            elapsed < Duration::from_secs(1),
1047            "join cancel took too long: {elapsed:?}"
1048        );
1049    }
1050
1051    #[test]
1052    fn cancellation_propagates_through_aggregate_consumer() {
1053        // An aggregate (e.g. COUNT) drains its upstream to completion. When
1054        // the upstream is cancel-aware, the aggregate's own pull surfaces
1055        // the Cancelled error rather than looping forever — i.e. the cancel
1056        // signal propagates *through* the aggregate without the aggregate
1057        // needing its own token.
1058        let token = CancelToken::new();
1059        let upstream: Box<dyn BindingIterator> = Box::new(
1060            QueryIterFilter::new(Box::new(InfiniteScan { n: 0 }), |_| false)
1061                .with_cancel(token.clone()),
1062        );
1063
1064        // Simulate an aggregate's drain loop.
1065        fn count_all(mut it: Box<dyn BindingIterator>) -> Result<u64, IterError> {
1066            let mut n = 0;
1067            while it.next_binding()?.is_some() {
1068                n += 1;
1069            }
1070            Ok(n)
1071        }
1072
1073        token.cancel();
1074        assert!(matches!(count_all(upstream), Err(IterError::Cancelled)));
1075    }
1076}