1use crate::iteration::comprehension::source::{LiteralValue, Source};
19use crate::iteration::comprehension::strategies::{Tuple, TupleValue};
20use crate::iteration::comprehension::strategy::{StrategyName, ZipMode};
21
22use super::op::{Op, OrderStreamingKind};
23use super::program::Program;
24
25pub trait TupleStream {
28 fn advance(&mut self) -> Option<Tuple>;
30}
31
32type BoxedStream = Box<dyn TupleStream>;
35
36pub fn interpret(program: &Program) -> BoxedStream {
41 let mut stack: Vec<BoxedStream> = Vec::new();
42 for op in program.ops() {
43 match op {
44 Op::PushClause { name, source } => {
45 stack.push(Box::new(ClauseStream::new(name.clone(), source.clone())));
46 }
47 Op::Cartesian { n } => {
48 let children = pop_n(&mut stack, *n);
49 stack.push(Box::new(CartesianStream::new(children)));
50 }
51 Op::Zip { n, mode } => {
52 let children = pop_n(&mut stack, *n);
53 stack.push(Box::new(ZipStream::new(children, *mode)));
54 }
55 Op::Union { n } => {
56 let children = pop_n(&mut stack, *n);
57 stack.push(Box::new(UnionStream::new(children)));
58 }
59 Op::Filter { predicate } => {
60 let inner = stack.pop().expect("Filter on empty stack");
61 stack.push(Box::new(FilterStream::new(inner, predicate.clone())));
62 }
63 Op::OrderStreaming { kind, truncation } => {
64 let inner = stack.pop().expect("OrderStreaming on empty stack");
65 stack.push(Box::new(OrderStreamingStream::new(
66 inner,
67 *kind,
68 *truncation,
69 )));
70 }
71 Op::OrderMaterialize {
72 strategy,
73 truncation,
74 indexed,
75 input_index_fn,
76 } => {
77 let inner = stack.pop().expect("OrderMaterialize on empty stack");
78 stack.push(Box::new(OrderMaterializeStream::new(
79 inner,
80 *strategy,
81 *truncation,
82 *indexed,
83 input_index_fn.clone(),
84 )));
85 }
86 Op::Dispense => {
87 }
90 }
91 }
92 stack.pop().expect("Program produced no result stream")
93}
94
95fn pop_n(stack: &mut Vec<BoxedStream>, n: usize) -> Vec<BoxedStream> {
96 assert!(
97 stack.len() >= n,
98 "stack underflow: needed {n}, have {}",
99 stack.len()
100 );
101 let split_at = stack.len() - n;
102 stack.split_off(split_at)
103}
104
105struct ClauseStream {
113 name: String,
114 state: ClauseState,
115}
116
117enum ClauseState {
118 LiteralList {
119 values: Vec<LiteralValue>,
120 pos: usize,
121 },
122 IntRange {
123 current: i64,
124 hi: i64,
125 step: i64,
126 },
127 Exhausted,
128}
129
130impl ClauseStream {
131 fn new(name: String, source: Source) -> Self {
132 let state = match source {
133 Source::Literal { values } => ClauseState::LiteralList { values, pos: 0 },
134 Source::IntRange { lo, hi, step } => ClauseState::IntRange {
135 current: lo,
136 hi,
137 step: step.max(1),
138 },
139 _ => ClauseState::Exhausted,
145 };
146 Self { name, state }
147 }
148}
149
150impl TupleStream for ClauseStream {
151 fn advance(&mut self) -> Option<Tuple> {
152 let value = match &mut self.state {
153 ClauseState::LiteralList { values, pos } => {
154 if *pos >= values.len() {
155 return None;
156 }
157 let v = literal_to_tuple_value(&values[*pos]);
158 *pos += 1;
159 v
160 }
161 ClauseState::IntRange { current, hi, step } => {
162 if *current >= *hi {
163 return None;
164 }
165 let v = TupleValue::I64(*current);
166 *current += *step;
167 v
168 }
169 ClauseState::Exhausted => return None,
170 };
171 Some(Tuple::new().with(self.name.clone(), value))
172 }
173}
174
175fn literal_to_tuple_value(lv: &LiteralValue) -> TupleValue {
176 match lv {
177 LiteralValue::Int(n) => TupleValue::I64(*n),
178 LiteralValue::Float(f) => TupleValue::F64(*f),
179 LiteralValue::String(s) => TupleValue::Str(s.clone()),
180 LiteralValue::Bool(b) => TupleValue::Bool(*b),
181 LiteralValue::Json(j) => TupleValue::Str(j.to_string()),
182 }
183}
184
185struct CartesianStream {
197 children: Vec<BoxedStream>,
198 cached: Vec<Vec<Tuple>>,
200 current_a0: Option<Tuple>,
202 cursors: Vec<usize>,
204 initialized: bool,
207 done: bool,
208}
209
210impl CartesianStream {
211 fn new(children: Vec<BoxedStream>) -> Self {
212 let n = children.len();
213 Self {
214 children,
215 cached: Vec::with_capacity(n.saturating_sub(1)),
216 current_a0: None,
217 cursors: vec![0; n.saturating_sub(1)],
218 initialized: false,
219 done: false,
220 }
221 }
222
223 fn initialize(&mut self) {
224 if self.children.is_empty() {
225 self.done = true;
226 return;
227 }
228 for i in 1..self.children.len() {
230 let mut v = Vec::new();
231 while let Some(t) = self.children[i].advance() {
232 v.push(t);
233 }
234 self.cached.push(v);
235 }
236 self.current_a0 = self.children[0].advance();
238 if self.current_a0.is_none() || self.cached.iter().any(|v| v.is_empty()) {
239 self.done = true;
241 }
242 }
243}
244
245impl TupleStream for CartesianStream {
246 fn advance(&mut self) -> Option<Tuple> {
247 if !self.initialized {
248 self.initialize();
249 self.initialized = true;
250 }
251 if self.done {
252 return None;
253 }
254 let mut out = Tuple::new();
256 if let Some(a0) = self.current_a0.as_ref() {
257 for (k, v) in &a0.bindings {
258 out.bindings.push((k.clone(), v.clone()));
259 }
260 }
261 for (i, cursor) in self.cursors.iter().enumerate() {
262 let tup = &self.cached[i][*cursor];
263 for (k, v) in &tup.bindings {
264 out.bindings.push((k.clone(), v.clone()));
265 }
266 }
267
268 let n_cached = self.cursors.len();
270 let mut overflow = true;
271 for i in (0..n_cached).rev() {
272 self.cursors[i] += 1;
273 if self.cursors[i] < self.cached[i].len() {
274 overflow = false;
275 break;
276 }
277 self.cursors[i] = 0;
278 }
279 if overflow {
280 self.current_a0 = self.children[0].advance();
282 if self.current_a0.is_none() {
283 self.done = true;
284 }
285 }
286 Some(out)
287 }
288}
289
290struct ZipStream {
293 children: Vec<BoxedStream>,
294 mode: ZipMode,
295 cycle_cache: Option<Vec<Vec<Tuple>>>,
297 cycle_cursors: Vec<usize>,
298 cycle_longest_idx: Option<usize>,
299 initialized: bool,
300 done: bool,
301}
302
303impl ZipStream {
304 fn new(children: Vec<BoxedStream>, mode: ZipMode) -> Self {
305 Self {
306 children,
307 mode,
308 cycle_cache: None,
309 cycle_cursors: Vec::new(),
310 cycle_longest_idx: None,
311 initialized: false,
312 done: false,
313 }
314 }
315
316 fn initialize_cycle(&mut self) {
317 let mut all: Vec<Vec<Tuple>> = Vec::with_capacity(self.children.len());
320 for child in &mut self.children {
321 let mut v = Vec::new();
322 while let Some(t) = child.advance() {
323 v.push(t);
324 }
325 all.push(v);
326 }
327 let longest_idx = all
328 .iter()
329 .enumerate()
330 .max_by_key(|(_, v)| v.len())
331 .map(|(i, _)| i)
332 .unwrap_or(0);
333 self.cycle_longest_idx = Some(longest_idx);
334 self.cycle_cursors = vec![0; all.len()];
335 self.cycle_cache = Some(all);
336 }
337}
338
339impl TupleStream for ZipStream {
340 fn advance(&mut self) -> Option<Tuple> {
341 if self.done {
342 return None;
343 }
344 match self.mode {
345 ZipMode::Strict | ZipMode::Truncate => {
346 let mut out = Tuple::new();
349 for child in &mut self.children {
350 match child.advance() {
351 Some(t) => {
352 for (k, v) in t.bindings {
353 out.bindings.push((k, v));
354 }
355 }
356 None => {
357 self.done = true;
358 return None;
359 }
360 }
361 }
362 Some(out)
363 }
364 ZipMode::Cycle => {
365 if !self.initialized {
366 self.initialize_cycle();
367 self.initialized = true;
368 }
369 let cache = self.cycle_cache.as_ref().unwrap();
370 let longest = self.cycle_longest_idx.unwrap();
371 if cache[longest].is_empty() {
372 self.done = true;
373 return None;
374 }
375 if self.cycle_cursors[longest] >= cache[longest].len() {
376 self.done = true;
377 return None;
378 }
379 let mut out = Tuple::new();
380 for (i, v) in cache.iter().enumerate() {
381 if v.is_empty() {
382 self.done = true;
384 return None;
385 }
386 let idx = self.cycle_cursors[i] % v.len();
387 for (k, val) in &v[idx].bindings {
388 out.bindings.push((k.clone(), val.clone()));
389 }
390 }
391 for c in self.cycle_cursors.iter_mut() {
393 *c += 1;
394 }
395 Some(out)
396 }
397 }
398 }
399}
400
401struct UnionStream {
405 children: Vec<BoxedStream>,
406 active_idx: usize,
407}
408
409impl UnionStream {
410 fn new(children: Vec<BoxedStream>) -> Self {
411 Self {
412 children,
413 active_idx: 0,
414 }
415 }
416}
417
418impl TupleStream for UnionStream {
419 fn advance(&mut self) -> Option<Tuple> {
420 loop {
421 if self.active_idx >= self.children.len() {
422 return None;
423 }
424 if let Some(t) = self.children[self.active_idx].advance() {
425 return Some(t);
426 }
427 self.active_idx += 1;
429 }
430 }
431}
432
433struct FilterStream {
436 inner: BoxedStream,
437 predicate: String,
438}
439
440impl FilterStream {
441 fn new(inner: BoxedStream, predicate: String) -> Self {
442 Self { inner, predicate }
443 }
444}
445
446impl TupleStream for FilterStream {
447 fn advance(&mut self) -> Option<Tuple> {
448 loop {
449 let candidate = self.inner.advance()?;
450 if evaluate_predicate(&self.predicate, &candidate) {
451 return Some(candidate);
452 }
453 }
454 }
455}
456
457struct OrderStreamingStream {
462 inner: BoxedStream,
463 truncation: Option<u64>,
464 emitted: u64,
465}
466
467impl OrderStreamingStream {
468 fn new(inner: BoxedStream, _kind: OrderStreamingKind, truncation: Option<u64>) -> Self {
469 Self {
470 inner,
471 truncation,
472 emitted: 0,
473 }
474 }
475}
476
477impl TupleStream for OrderStreamingStream {
478 fn advance(&mut self) -> Option<Tuple> {
479 if let Some(cap) = self.truncation
480 && self.emitted >= cap
481 {
482 return None;
483 }
484 let t = self.inner.advance()?;
485 self.emitted += 1;
486 Some(t)
487 }
488}
489
490struct OrderMaterializeStream {
501 inner: BoxedStream,
502 strategy: StrategyName,
503 truncation: Option<u64>,
504 #[allow(dead_code)] indexed: bool,
506 input_index_fn: Option<crate::iteration::comprehension::metadata::IndexFn>,
507 materialized: Option<Vec<Tuple>>,
508 pos: usize,
509}
510
511impl OrderMaterializeStream {
512 fn new(
513 inner: BoxedStream,
514 strategy: StrategyName,
515 truncation: Option<u64>,
516 indexed: bool,
517 input_index_fn: Option<crate::iteration::comprehension::metadata::IndexFn>,
518 ) -> Self {
519 Self {
520 inner,
521 strategy,
522 truncation,
523 indexed,
524 input_index_fn,
525 materialized: None,
526 pos: 0,
527 }
528 }
529
530 fn materialize(&mut self) {
531 let mut buf = Vec::new();
532 while let Some(t) = self.inner.advance() {
533 buf.push(t);
534 }
535 let cardinality = buf.len() as u64;
536 let index_fn = self.input_index_fn.clone().unwrap_or(
537 crate::iteration::comprehension::metadata::IndexFn::Lattice {
538 axis_sizes: vec![cardinality],
539 },
540 );
541 let input = crate::iteration::comprehension::strategies::EvaluatedInput {
542 tuples: buf,
543 cardinality,
544 index_fn,
545 };
546 let dispatched = crate::iteration::comprehension::strategies::for_name(self.strategy);
547 let out = dispatched.apply(&input, self.truncation);
548 self.materialized = Some(out);
549 }
550}
551
552impl TupleStream for OrderMaterializeStream {
553 fn advance(&mut self) -> Option<Tuple> {
554 if self.materialized.is_none() {
555 self.materialize();
556 }
557 let buf = self.materialized.as_ref().unwrap();
558 if self.pos >= buf.len() {
559 return None;
560 }
561 let t = buf[self.pos].clone();
562 self.pos += 1;
563 Some(t)
564 }
565}
566
567fn evaluate_predicate(predicate: &str, tuple: &Tuple) -> bool {
587 let trimmed = predicate.trim();
588 if trimmed.eq_ignore_ascii_case("true") {
589 return true;
590 }
591 if trimmed.eq_ignore_ascii_case("false") {
592 return false;
593 }
594 if let Some(inner) = trimmed.strip_prefix('!') {
596 return !evaluate_predicate(inner.trim(), tuple);
597 }
598 if let Some(parts) = split_top_level(trimmed, "&&") {
600 return parts.iter().all(|p| evaluate_predicate(p, tuple));
601 }
602 if let Some(parts) = split_top_level(trimmed, "||") {
604 return parts.iter().any(|p| evaluate_predicate(p, tuple));
605 }
606 if let Some(in_pos) = trimmed.find(" in ") {
608 let lhs = trimmed[..in_pos].trim();
609 let rhs = trimmed[in_pos + 4..].trim();
610 if let Some(name) = strip_curly(lhs)
611 && let Some(inner) = rhs.strip_prefix('[').and_then(|s| s.strip_suffix(']'))
612 {
613 let needle = lookup(tuple, &name);
614 if needle.is_none() {
615 return true; }
617 return inner.split(',').any(|item| {
618 parse_literal(item.trim())
619 .map(|v| values_eq(&lit_to_tuple_value(&v), needle.unwrap()))
620 .unwrap_or(false)
621 });
622 }
623 }
624 for (op, op_kind) in [
626 ("==", CmpKind::Eq),
627 ("!=", CmpKind::Ne),
628 ("<=", CmpKind::Le),
629 (">=", CmpKind::Ge),
630 ("<", CmpKind::Lt),
631 (">", CmpKind::Gt),
632 ] {
633 if let Some((lhs, rhs)) = split_top_level_op(trimmed, op) {
634 let lhs = lhs.trim();
635 let rhs = rhs.trim();
636 if let (Some(name), Some(lit)) = (strip_curly(lhs), parse_literal(rhs)) {
638 let val = lookup(tuple, &name);
639 if val.is_none() {
640 return true;
641 }
642 return compare(val.unwrap(), op_kind, &lit_to_tuple_value(&lit));
643 }
644 if let (Some(name), Some(lit)) = (strip_curly(rhs), parse_literal(lhs)) {
646 let val = lookup(tuple, &name);
647 if val.is_none() {
648 return true;
649 }
650 let inv = invert_kind(op_kind);
652 return compare(val.unwrap(), inv, &lit_to_tuple_value(&lit));
653 }
654 if let (Some(a), Some(b)) = (strip_curly(lhs), strip_curly(rhs)) {
656 let va = lookup(tuple, &a);
657 let vb = lookup(tuple, &b);
658 if va.is_none() || vb.is_none() {
659 return true;
660 }
661 return compare(va.unwrap(), op_kind, vb.unwrap());
662 }
663 }
664 }
665 true
666}
667
668#[derive(Clone, Copy)]
669enum CmpKind {
670 Eq,
671 Ne,
672 Lt,
673 Le,
674 Gt,
675 Ge,
676}
677
678fn invert_kind(k: CmpKind) -> CmpKind {
679 match k {
680 CmpKind::Lt => CmpKind::Gt,
681 CmpKind::Le => CmpKind::Ge,
682 CmpKind::Gt => CmpKind::Lt,
683 CmpKind::Ge => CmpKind::Le,
684 other => other,
685 }
686}
687
688fn lookup<'a>(tuple: &'a Tuple, name: &str) -> Option<&'a TupleValue> {
689 tuple
690 .bindings
691 .iter()
692 .find(|(k, _)| k == name)
693 .map(|(_, v)| v)
694}
695
696fn compare(a: &TupleValue, kind: CmpKind, b: &TupleValue) -> bool {
697 let ord = match (a, b) {
698 (TupleValue::I64(a), TupleValue::I64(b)) => a.cmp(b),
699 (TupleValue::U64(a), TupleValue::U64(b)) => a.cmp(b),
700 (TupleValue::F64(a), TupleValue::F64(b)) => {
701 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
702 }
703 (TupleValue::I64(a), TupleValue::F64(b)) => (*a as f64)
704 .partial_cmp(b)
705 .unwrap_or(std::cmp::Ordering::Equal),
706 (TupleValue::F64(a), TupleValue::I64(b)) => a
707 .partial_cmp(&(*b as f64))
708 .unwrap_or(std::cmp::Ordering::Equal),
709 (TupleValue::Str(a), TupleValue::Str(b)) => a.cmp(b),
710 (TupleValue::Bool(a), TupleValue::Bool(b)) => a.cmp(b),
711 _ => return false,
712 };
713 match kind {
714 CmpKind::Eq => ord.is_eq(),
715 CmpKind::Ne => !ord.is_eq(),
716 CmpKind::Lt => ord.is_lt(),
717 CmpKind::Le => ord.is_le(),
718 CmpKind::Gt => ord.is_gt(),
719 CmpKind::Ge => ord.is_ge(),
720 }
721}
722
723fn values_eq(a: &TupleValue, b: &TupleValue) -> bool {
724 compare(a, CmpKind::Eq, b)
725}
726
727fn strip_curly(s: &str) -> Option<String> {
728 let s = s.trim();
729 if s.starts_with('{') && s.ends_with('}') {
730 let inner = &s[1..s.len() - 1];
731 let trimmed = inner.trim();
732 if trimmed.chars().all(|c| c.is_alphanumeric() || c == '_') && !trimmed.is_empty() {
733 return Some(trimmed.to_string());
734 }
735 }
736 None
737}
738
739fn parse_literal(s: &str) -> Option<LiteralValue> {
740 let s = s.trim();
741 if s.eq_ignore_ascii_case("true") {
742 return Some(LiteralValue::Bool(true));
743 }
744 if s.eq_ignore_ascii_case("false") {
745 return Some(LiteralValue::Bool(false));
746 }
747 if s.len() >= 2
748 && ((s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')))
749 {
750 return Some(LiteralValue::String(s[1..s.len() - 1].to_string()));
751 }
752 if let Ok(n) = s.parse::<i64>() {
753 return Some(LiteralValue::Int(n));
754 }
755 if let Ok(f) = s.parse::<f64>() {
756 return Some(LiteralValue::Float(f));
757 }
758 None
759}
760
761fn lit_to_tuple_value(lv: &LiteralValue) -> TupleValue {
762 literal_to_tuple_value(lv)
763}
764
765fn split_top_level(s: &str, sep: &str) -> Option<Vec<String>> {
766 let mut parts = Vec::new();
767 let mut depth = 0i64;
768 let mut last = 0usize;
769 let bytes = s.as_bytes();
770 let sep_bytes = sep.as_bytes();
771 let mut i = 0;
772 while i < bytes.len() {
773 match bytes[i] {
774 b'(' | b'[' | b'{' => depth += 1,
775 b')' | b']' | b'}' => depth -= 1,
776 _ => {}
777 }
778 if depth == 0
779 && i + sep_bytes.len() <= bytes.len()
780 && &bytes[i..i + sep_bytes.len()] == sep_bytes
781 {
782 parts.push(s[last..i].trim().to_string());
783 last = i + sep_bytes.len();
784 i = last;
785 continue;
786 }
787 i += 1;
788 }
789 if parts.is_empty() {
790 return None;
791 }
792 parts.push(s[last..].trim().to_string());
793 Some(parts)
794}
795
796fn split_top_level_op<'a>(s: &'a str, op: &str) -> Option<(&'a str, &'a str)> {
797 let mut depth = 0i64;
798 let bytes = s.as_bytes();
799 let op_bytes = op.as_bytes();
800 let mut i = 0;
801 while i < bytes.len() {
802 match bytes[i] {
803 b'(' | b'[' | b'{' => depth += 1,
804 b')' | b']' | b'}' => depth -= 1,
805 _ => {}
806 }
807 if depth == 0
808 && i + op_bytes.len() <= bytes.len()
809 && &bytes[i..i + op_bytes.len()] == op_bytes
810 {
811 if op.len() == 1 {
812 let next = bytes.get(i + 1).copied();
813 if next == Some(b'=') {
814 i += 1;
815 continue;
816 }
817 }
818 return Some((&s[..i], &s[i + op_bytes.len()..]));
819 }
820 i += 1;
821 }
822 None
823}
824
825#[cfg(test)]
826mod tests {
827 use super::*;
828 use crate::iteration::comprehension::ast::Comprehension;
829 use crate::iteration::comprehension::ir::compile;
830 use crate::iteration::comprehension::source::{LiteralValue, Source};
831
832 fn clause(name: &str, vs: &[i64]) -> Comprehension {
833 Comprehension::clause(
834 name,
835 Source::Literal {
836 values: vs.iter().map(|n| LiteralValue::Int(*n)).collect(),
837 },
838 )
839 }
840
841 fn collect(stream: &mut BoxedStream) -> Vec<Tuple> {
842 let mut out = Vec::new();
843 while let Some(t) = stream.advance() {
844 out.push(t);
845 }
846 out
847 }
848
849 #[test]
850 fn single_clause_dispense() {
851 let ast = clause("k", &[1, 2, 3]);
852 let prog = compile(&ast);
853 let mut stream = interpret(&prog);
854 let tuples = collect(&mut stream);
855 assert_eq!(tuples.len(), 3);
856 assert_eq!(tuples[0].bindings[0].1, TupleValue::I64(1));
857 assert_eq!(tuples[2].bindings[0].1, TupleValue::I64(3));
858 }
859
860 #[test]
861 fn cartesian_2d_lex_order() {
862 let ast = Comprehension::cartesian(vec![clause("a", &[1, 2]), clause("b", &[10, 20])]);
863 let prog = compile(&ast);
864 let mut stream = interpret(&prog);
865 let tuples = collect(&mut stream);
866 assert_eq!(tuples.len(), 4);
867 assert_eq!(tuples[0].bindings[0].1, TupleValue::I64(1));
869 assert_eq!(tuples[0].bindings[1].1, TupleValue::I64(10));
870 assert_eq!(tuples[1].bindings[1].1, TupleValue::I64(20));
871 assert_eq!(tuples[2].bindings[0].1, TupleValue::I64(2));
872 }
873
874 #[test]
875 fn zip_strict_3() {
876 let ast = Comprehension::zip(
877 vec![clause("x", &[1, 2, 3]), clause("y", &[10, 20, 30])],
878 ZipMode::Strict,
879 );
880 let prog = compile(&ast);
881 let mut stream = interpret(&prog);
882 let tuples = collect(&mut stream);
883 assert_eq!(tuples.len(), 3);
884 assert_eq!(tuples[0].bindings[0].1, TupleValue::I64(1));
885 assert_eq!(tuples[0].bindings[1].1, TupleValue::I64(10));
886 assert_eq!(tuples[2].bindings[1].1, TupleValue::I64(30));
887 }
888
889 #[test]
890 fn zip_truncate_shortest() {
891 let ast = Comprehension::zip(
892 vec![clause("x", &[1, 2, 3, 4]), clause("y", &[10, 20])],
893 ZipMode::Truncate,
894 );
895 let prog = compile(&ast);
896 let mut stream = interpret(&prog);
897 let tuples = collect(&mut stream);
898 assert_eq!(tuples.len(), 2);
899 }
900
901 #[test]
902 fn union_drains_in_order() {
903 let ast = Comprehension::union(vec![clause("k", &[1, 2]), clause("k", &[10, 20])]);
904 let prog = compile(&ast);
905 let mut stream = interpret(&prog);
906 let tuples = collect(&mut stream);
907 assert_eq!(tuples.len(), 4);
908 assert_eq!(tuples[0].bindings[0].1, TupleValue::I64(1));
909 assert_eq!(tuples[2].bindings[0].1, TupleValue::I64(10));
910 }
911
912 #[test]
913 fn filter_keeps_only_matching() {
914 let cart = Comprehension::cartesian(vec![clause("k", &[1, 2, 3, 4, 5])]);
915 let ast = Comprehension::filter(cart, "{k} > 2");
916 let prog = compile(&ast);
917 let mut stream = interpret(&prog);
918 let tuples = collect(&mut stream);
919 assert_eq!(tuples.len(), 3);
920 for t in &tuples {
921 match t.bindings[0].1 {
922 TupleValue::I64(n) => assert!(n > 2),
923 _ => panic!(),
924 }
925 }
926 }
927
928 #[test]
929 fn order_streaming_lex_truncates() {
930 let ast = Comprehension::order(clause("k", &[1, 2, 3, 4, 5]), StrategyName::Lex, Some(2));
931 let prog = compile(&ast);
932 let mut stream = interpret(&prog);
933 let tuples = collect(&mut stream);
934 assert_eq!(tuples.len(), 2);
935 assert_eq!(tuples[0].bindings[0].1, TupleValue::I64(1));
936 assert_eq!(tuples[1].bindings[0].1, TupleValue::I64(2));
937 }
938
939 #[test]
940 fn order_materialize_shuffle_produces_full_set() {
941 let ast = Comprehension::order(clause("k", &[1, 2, 3, 4, 5]), StrategyName::Shuffle, None);
942 let prog = compile(&ast);
943 let mut stream = interpret(&prog);
944 let tuples = collect(&mut stream);
945 assert_eq!(tuples.len(), 5);
946 let mut sorted: Vec<i64> = tuples
948 .iter()
949 .map(|t| match t.bindings[0].1 {
950 TupleValue::I64(n) => n,
951 _ => panic!(),
952 })
953 .collect();
954 sorted.sort();
955 assert_eq!(sorted, vec![1, 2, 3, 4, 5]);
956 }
957
958 #[test]
959 fn dispense_sequence_for_section_11_1() {
960 let ast = Comprehension::cartesian(vec![clause("k", &[1, 2]), clause("b", &[10, 20])]);
962 let prog = compile(&ast);
963 let mut stream = interpret(&prog);
964 let tuples = collect(&mut stream);
965 assert_eq!(tuples.len(), 4);
967 }
968}