Skip to main content

reddb_server/storage/query/engine/
transform.rs

1//! Op Tree Transformations
2//!
3//! Visitor and transformer patterns for query algebra trees.
4//!
5//! # Patterns
6//!
7//! - **OpVisitor**: Read-only traversal for analysis
8//! - **OpTransform**: Transform tree nodes (returns new tree)
9//! - **TransformCopy**: Default copy transform (base for custom transforms)
10//! - **TransformPushFilter**: Push filters down for early evaluation
11
12use super::binding::Var;
13use super::op::*;
14
15/// Visitor trait for walking Op trees (read-only)
16pub trait OpVisitor {
17    /// Visit BGP
18    fn visit_bgp(&mut self, _op: &OpBGP) {}
19
20    /// Visit triple
21    fn visit_triple(&mut self, _op: &OpTriple) {}
22
23    /// Visit join
24    fn visit_join(&mut self, _op: &OpJoin) {}
25
26    /// Visit left join
27    fn visit_left_join(&mut self, _op: &OpLeftJoin) {}
28
29    /// Visit filter
30    fn visit_filter(&mut self, _op: &OpFilter) {}
31
32    /// Visit union
33    fn visit_union(&mut self, _op: &OpUnion) {}
34
35    /// Visit project
36    fn visit_project(&mut self, _op: &OpProject) {}
37
38    /// Visit distinct
39    fn visit_distinct(&mut self, _op: &OpDistinct) {}
40
41    /// Visit reduced
42    fn visit_reduced(&mut self, _op: &OpReduced) {}
43
44    /// Visit slice
45    fn visit_slice(&mut self, _op: &OpSlice) {}
46
47    /// Visit order
48    fn visit_order(&mut self, _op: &OpOrder) {}
49
50    /// Visit group
51    fn visit_group(&mut self, _op: &OpGroup) {}
52
53    /// Visit extend
54    fn visit_extend(&mut self, _op: &OpExtend) {}
55
56    /// Visit minus
57    fn visit_minus(&mut self, _op: &OpMinus) {}
58
59    /// Visit right join
60    fn visit_right_join(&mut self, _op: &OpRightJoin) {}
61
62    /// Visit cross join
63    fn visit_cross_join(&mut self, _op: &OpCrossJoin) {}
64
65    /// Visit intersect
66    fn visit_intersect(&mut self, _op: &OpIntersect) {}
67
68    /// Visit table
69    fn visit_table(&mut self, _op: &OpTable) {}
70
71    /// Visit sequence
72    fn visit_sequence(&mut self, _op: &OpSequence) {}
73
74    /// Visit disjunction
75    fn visit_disjunction(&mut self, _op: &OpDisjunction) {}
76
77    /// Visit null
78    fn visit_null(&mut self, _op: &OpNull) {}
79}
80
81/// Walk an Op tree with a visitor
82pub fn walk_op<V: OpVisitor>(visitor: &mut V, op: &Op) {
83    match op {
84        Op::BGP(o) => {
85            visitor.visit_bgp(o);
86        }
87        Op::Triple(o) => {
88            visitor.visit_triple(o);
89        }
90        Op::Join(o) => {
91            walk_op(visitor, &o.left);
92            walk_op(visitor, &o.right);
93            visitor.visit_join(o);
94        }
95        Op::LeftJoin(o) => {
96            walk_op(visitor, &o.left);
97            walk_op(visitor, &o.right);
98            visitor.visit_left_join(o);
99        }
100        Op::Filter(o) => {
101            walk_op(visitor, &o.sub_op);
102            visitor.visit_filter(o);
103        }
104        Op::Union(o) => {
105            walk_op(visitor, &o.left);
106            walk_op(visitor, &o.right);
107            visitor.visit_union(o);
108        }
109        Op::Project(o) => {
110            walk_op(visitor, &o.sub_op);
111            visitor.visit_project(o);
112        }
113        Op::Distinct(o) => {
114            walk_op(visitor, &o.sub_op);
115            visitor.visit_distinct(o);
116        }
117        Op::Reduced(o) => {
118            walk_op(visitor, &o.sub_op);
119            visitor.visit_reduced(o);
120        }
121        Op::Slice(o) => {
122            walk_op(visitor, &o.sub_op);
123            visitor.visit_slice(o);
124        }
125        Op::Order(o) => {
126            walk_op(visitor, &o.sub_op);
127            visitor.visit_order(o);
128        }
129        Op::Group(o) => {
130            walk_op(visitor, &o.sub_op);
131            visitor.visit_group(o);
132        }
133        Op::Extend(o) => {
134            walk_op(visitor, &o.sub_op);
135            visitor.visit_extend(o);
136        }
137        Op::Minus(o) => {
138            walk_op(visitor, &o.left);
139            walk_op(visitor, &o.right);
140            visitor.visit_minus(o);
141        }
142        Op::RightJoin(o) => {
143            walk_op(visitor, &o.left);
144            walk_op(visitor, &o.right);
145            visitor.visit_right_join(o);
146        }
147        Op::CrossJoin(o) => {
148            walk_op(visitor, &o.left);
149            walk_op(visitor, &o.right);
150            visitor.visit_cross_join(o);
151        }
152        Op::Intersect(o) => {
153            walk_op(visitor, &o.left);
154            walk_op(visitor, &o.right);
155            visitor.visit_intersect(o);
156        }
157        Op::Table(o) => {
158            visitor.visit_table(o);
159        }
160        Op::Sequence(o) => {
161            for sub in &o.ops {
162                walk_op(visitor, sub);
163            }
164            visitor.visit_sequence(o);
165        }
166        Op::Disjunction(o) => {
167            for sub in &o.ops {
168                walk_op(visitor, sub);
169            }
170            visitor.visit_disjunction(o);
171        }
172        Op::Null(o) => {
173            visitor.visit_null(o);
174        }
175    }
176}
177
178/// Transformer trait for transforming Op trees
179pub trait OpTransform {
180    /// Transform BGP
181    fn transform_bgp(&mut self, op: OpBGP) -> Op {
182        Op::BGP(op)
183    }
184
185    /// Transform triple
186    fn transform_triple(&mut self, op: OpTriple) -> Op {
187        Op::Triple(op)
188    }
189
190    /// Transform join
191    fn transform_join(&mut self, left: Op, right: Op, _original: &OpJoin) -> Op {
192        Op::Join(OpJoin::new(left, right))
193    }
194
195    /// Transform left join
196    fn transform_left_join(&mut self, left: Op, right: Op, original: &OpLeftJoin) -> Op {
197        Op::LeftJoin(OpLeftJoin {
198            left: Box::new(left),
199            right: Box::new(right),
200            filter: original.filter.clone(),
201        })
202    }
203
204    /// Transform right join
205    fn transform_right_join(&mut self, left: Op, right: Op, original: &OpRightJoin) -> Op {
206        Op::RightJoin(OpRightJoin {
207            left: Box::new(left),
208            right: Box::new(right),
209            filter: original.filter.clone(),
210        })
211    }
212
213    /// Transform cross join
214    fn transform_cross_join(&mut self, left: Op, right: Op, _original: &OpCrossJoin) -> Op {
215        Op::CrossJoin(OpCrossJoin {
216            left: Box::new(left),
217            right: Box::new(right),
218        })
219    }
220
221    /// Transform intersect
222    fn transform_intersect(&mut self, left: Op, right: Op, _original: &OpIntersect) -> Op {
223        Op::Intersect(OpIntersect {
224            left: Box::new(left),
225            right: Box::new(right),
226        })
227    }
228
229    /// Transform filter
230    fn transform_filter(&mut self, sub_op: Op, original: &OpFilter) -> Op {
231        Op::Filter(OpFilter {
232            filter: original.filter.clone(),
233            sub_op: Box::new(sub_op),
234        })
235    }
236
237    /// Transform union
238    fn transform_union(&mut self, left: Op, right: Op, _original: &OpUnion) -> Op {
239        Op::Union(OpUnion::new(left, right))
240    }
241
242    /// Transform project
243    fn transform_project(&mut self, sub_op: Op, original: &OpProject) -> Op {
244        Op::Project(OpProject {
245            vars: original.vars.clone(),
246            sub_op: Box::new(sub_op),
247        })
248    }
249
250    /// Transform distinct
251    fn transform_distinct(&mut self, sub_op: Op, _original: &OpDistinct) -> Op {
252        Op::Distinct(OpDistinct::new(sub_op))
253    }
254
255    /// Transform reduced
256    fn transform_reduced(&mut self, sub_op: Op, _original: &OpReduced) -> Op {
257        Op::Reduced(OpReduced::new(sub_op))
258    }
259
260    /// Transform slice
261    fn transform_slice(&mut self, sub_op: Op, original: &OpSlice) -> Op {
262        Op::Slice(OpSlice::new(sub_op, original.offset, original.limit))
263    }
264
265    /// Transform order
266    fn transform_order(&mut self, sub_op: Op, original: &OpOrder) -> Op {
267        Op::Order(OpOrder {
268            sub_op: Box::new(sub_op),
269            keys: original.keys.clone(),
270        })
271    }
272
273    /// Transform group
274    fn transform_group(&mut self, sub_op: Op, original: &OpGroup) -> Op {
275        Op::Group(OpGroup {
276            sub_op: Box::new(sub_op),
277            group_vars: original.group_vars.clone(),
278            aggregates: original.aggregates.clone(),
279        })
280    }
281
282    /// Transform extend
283    fn transform_extend(&mut self, sub_op: Op, original: &OpExtend) -> Op {
284        Op::Extend(OpExtend {
285            sub_op: Box::new(sub_op),
286            var: original.var.clone(),
287            expr: original.expr.clone(),
288        })
289    }
290
291    /// Transform minus
292    fn transform_minus(&mut self, left: Op, right: Op, _original: &OpMinus) -> Op {
293        Op::Minus(OpMinus::new(left, right))
294    }
295
296    /// Transform table
297    fn transform_table(&mut self, op: OpTable) -> Op {
298        Op::Table(op)
299    }
300
301    /// Transform sequence
302    fn transform_sequence(&mut self, ops: Vec<Op>, _original: &OpSequence) -> Op {
303        Op::Sequence(OpSequence::new(ops))
304    }
305
306    /// Transform disjunction
307    fn transform_disjunction(&mut self, ops: Vec<Op>, _original: &OpDisjunction) -> Op {
308        Op::Disjunction(OpDisjunction::new(ops))
309    }
310
311    /// Transform null
312    fn transform_null(&mut self, op: OpNull) -> Op {
313        Op::Null(op)
314    }
315}
316
317/// Apply a transform to an Op tree (bottom-up)
318pub fn transform_op<T: OpTransform>(transformer: &mut T, op: Op) -> Op {
319    match op {
320        Op::BGP(o) => transformer.transform_bgp(o),
321        Op::Triple(o) => transformer.transform_triple(o),
322        Op::Join(o) => {
323            let orig = o.clone();
324            let left = transform_op(transformer, *o.left);
325            let right = transform_op(transformer, *o.right);
326            transformer.transform_join(left, right, &orig)
327        }
328        Op::LeftJoin(o) => {
329            let orig = o.clone();
330            let left = transform_op(transformer, *o.left);
331            let right = transform_op(transformer, *o.right);
332            transformer.transform_left_join(left, right, &orig)
333        }
334        Op::Filter(o) => {
335            let orig = o.clone();
336            let sub_op = transform_op(transformer, *o.sub_op);
337            transformer.transform_filter(sub_op, &orig)
338        }
339        Op::Union(o) => {
340            let orig = o.clone();
341            let left = transform_op(transformer, *o.left);
342            let right = transform_op(transformer, *o.right);
343            transformer.transform_union(left, right, &orig)
344        }
345        Op::Project(o) => {
346            let orig = o.clone();
347            let sub_op = transform_op(transformer, *o.sub_op);
348            transformer.transform_project(sub_op, &orig)
349        }
350        Op::Distinct(o) => {
351            let orig = o.clone();
352            let sub_op = transform_op(transformer, *o.sub_op);
353            transformer.transform_distinct(sub_op, &orig)
354        }
355        Op::Reduced(o) => {
356            let orig = o.clone();
357            let sub_op = transform_op(transformer, *o.sub_op);
358            transformer.transform_reduced(sub_op, &orig)
359        }
360        Op::Slice(o) => {
361            let orig = o.clone();
362            let sub_op = transform_op(transformer, *o.sub_op);
363            transformer.transform_slice(sub_op, &orig)
364        }
365        Op::Order(o) => {
366            let orig = o.clone();
367            let sub_op = transform_op(transformer, *o.sub_op);
368            transformer.transform_order(sub_op, &orig)
369        }
370        Op::Group(o) => {
371            let orig = o.clone();
372            let sub_op = transform_op(transformer, *o.sub_op);
373            transformer.transform_group(sub_op, &orig)
374        }
375        Op::Extend(o) => {
376            let orig = o.clone();
377            let sub_op = transform_op(transformer, *o.sub_op);
378            transformer.transform_extend(sub_op, &orig)
379        }
380        Op::Minus(o) => {
381            let orig = o.clone();
382            let left = transform_op(transformer, *o.left);
383            let right = transform_op(transformer, *o.right);
384            transformer.transform_minus(left, right, &orig)
385        }
386        Op::RightJoin(o) => {
387            let orig = o.clone();
388            let left = transform_op(transformer, *o.left);
389            let right = transform_op(transformer, *o.right);
390            transformer.transform_right_join(left, right, &orig)
391        }
392        Op::CrossJoin(o) => {
393            let orig = o.clone();
394            let left = transform_op(transformer, *o.left);
395            let right = transform_op(transformer, *o.right);
396            transformer.transform_cross_join(left, right, &orig)
397        }
398        Op::Intersect(o) => {
399            let orig = o.clone();
400            let left = transform_op(transformer, *o.left);
401            let right = transform_op(transformer, *o.right);
402            transformer.transform_intersect(left, right, &orig)
403        }
404        Op::Table(o) => transformer.transform_table(o),
405        Op::Sequence(o) => {
406            let ops: Vec<Op> = o
407                .ops
408                .into_iter()
409                .map(|sub| transform_op(transformer, sub))
410                .collect();
411            transformer.transform_sequence(ops, &OpSequence::new(Vec::new()))
412        }
413        Op::Disjunction(o) => {
414            let ops: Vec<Op> = o
415                .ops
416                .into_iter()
417                .map(|sub| transform_op(transformer, sub))
418                .collect();
419            transformer.transform_disjunction(ops, &OpDisjunction::new(Vec::new()))
420        }
421        Op::Null(o) => transformer.transform_null(o),
422    }
423}
424
425/// Default copy transform (identity transformation)
426#[derive(Debug, Default)]
427pub struct TransformCopy;
428
429impl TransformCopy {
430    /// Create new copy transform
431    pub fn new() -> Self {
432        Self
433    }
434}
435
436impl OpTransform for TransformCopy {}
437
438/// Filter pushdown transform
439///
440/// Pushes filters down the tree to evaluate them as early as possible.
441/// This reduces intermediate result sizes.
442#[derive(Debug, Default)]
443pub struct TransformPushFilter {
444    /// Filters to push down
445    pending_filters: Vec<FilterExpr>,
446}
447
448impl TransformPushFilter {
449    /// Create new pushdown transform
450    pub fn new() -> Self {
451        Self {
452            pending_filters: Vec::new(),
453        }
454    }
455
456    /// Check if filter uses only variables from the given set
457    fn filter_uses_only(&self, filter: &FilterExpr, vars: &[Var]) -> bool {
458        let filter_vars = self.extract_filter_vars(filter);
459        filter_vars.iter().all(|v| vars.contains(v))
460    }
461
462    /// Extract variables used in a filter
463    fn extract_filter_vars(&self, filter: &FilterExpr) -> Vec<Var> {
464        let mut vars = Vec::new();
465        Self::collect_filter_vars(filter, &mut vars);
466        vars
467    }
468
469    fn collect_filter_vars(filter: &FilterExpr, vars: &mut Vec<Var>) {
470        match filter {
471            FilterExpr::Eq(l, r)
472            | FilterExpr::NotEq(l, r)
473            | FilterExpr::Lt(l, r)
474            | FilterExpr::LtEq(l, r)
475            | FilterExpr::Gt(l, r)
476            | FilterExpr::GtEq(l, r) => {
477                Self::collect_term_vars(l, vars);
478                Self::collect_term_vars(r, vars);
479            }
480            FilterExpr::And(l, r) | FilterExpr::Or(l, r) => {
481                Self::collect_filter_vars(l, vars);
482                Self::collect_filter_vars(r, vars);
483            }
484            FilterExpr::Not(e) => {
485                Self::collect_filter_vars(e, vars);
486            }
487            FilterExpr::Bound(v) => {
488                if !vars.contains(v) {
489                    vars.push(v.clone());
490                }
491            }
492            FilterExpr::Regex(t, _, _)
493            | FilterExpr::StartsWith(t, _)
494            | FilterExpr::EndsWith(t, _)
495            | FilterExpr::Contains(t, _)
496            | FilterExpr::IsUri(t)
497            | FilterExpr::IsLiteral(t)
498            | FilterExpr::IsBlank(t) => {
499                Self::collect_term_vars(t, vars);
500            }
501            FilterExpr::In(t, list) | FilterExpr::NotIn(t, list) => {
502                Self::collect_term_vars(t, vars);
503                for item in list {
504                    Self::collect_term_vars(item, vars);
505                }
506            }
507            FilterExpr::True | FilterExpr::False => {}
508        }
509    }
510
511    fn collect_term_vars(term: &ExprTerm, vars: &mut Vec<Var>) {
512        match term {
513            ExprTerm::Var(v) => {
514                if !vars.contains(v) {
515                    vars.push(v.clone());
516                }
517            }
518            ExprTerm::Str(inner)
519            | ExprTerm::LCase(inner)
520            | ExprTerm::UCase(inner)
521            | ExprTerm::StrLen(inner) => {
522                Self::collect_term_vars(inner, vars);
523            }
524            ExprTerm::Concat(terms) => {
525                for t in terms {
526                    Self::collect_term_vars(t, vars);
527                }
528            }
529            ExprTerm::Const(_) => {}
530        }
531    }
532
533    /// Wrap op with pending filters that apply to it
534    fn apply_pending_filters(&mut self, op: Op) -> Op {
535        if self.pending_filters.is_empty() {
536            return op;
537        }
538
539        let op_vars = op.vars();
540        let mut applicable = Vec::new();
541        let mut remaining = Vec::new();
542
543        // Collect filters first to avoid borrow conflict
544        let filters: Vec<_> = self.pending_filters.drain(..).collect();
545        for filter in filters {
546            if self.filter_uses_only(&filter, &op_vars) {
547                applicable.push(filter);
548            } else {
549                remaining.push(filter);
550            }
551        }
552
553        self.pending_filters = remaining;
554
555        let mut result = op;
556        for filter in applicable {
557            result = Op::Filter(OpFilter::new(filter, result));
558        }
559        result
560    }
561}
562
563impl OpTransform for TransformPushFilter {
564    fn transform_filter(&mut self, sub_op: Op, original: &OpFilter) -> Op {
565        // Collect filter and continue transformation
566        self.pending_filters.push(original.filter.clone());
567        self.apply_pending_filters(sub_op)
568    }
569
570    fn transform_join(&mut self, left: Op, right: Op, _original: &OpJoin) -> Op {
571        // Try to push filters to either side
572        let left = self.apply_pending_filters(left);
573        let right = self.apply_pending_filters(right);
574
575        let result = Op::Join(OpJoin::new(left, right));
576        self.apply_pending_filters(result)
577    }
578
579    fn transform_bgp(&mut self, op: OpBGP) -> Op {
580        self.apply_pending_filters(Op::BGP(op))
581    }
582
583    fn transform_triple(&mut self, op: OpTriple) -> Op {
584        self.apply_pending_filters(Op::Triple(op))
585    }
586
587    fn transform_table(&mut self, op: OpTable) -> Op {
588        self.apply_pending_filters(Op::Table(op))
589    }
590}
591
592/// Variable collector visitor
593#[derive(Debug, Default)]
594pub struct VarCollector {
595    pub vars: Vec<Var>,
596}
597
598impl VarCollector {
599    /// Create new collector
600    pub fn new() -> Self {
601        Self { vars: Vec::new() }
602    }
603
604    /// Collect vars from op
605    pub fn collect(op: &Op) -> Vec<Var> {
606        let mut collector = Self::new();
607        walk_op(&mut collector, op);
608        collector.vars
609    }
610
611    fn add_var(&mut self, var: &Var) {
612        if !self.vars.contains(var) {
613            self.vars.push(var.clone());
614        }
615    }
616}
617
618impl OpVisitor for VarCollector {
619    fn visit_bgp(&mut self, op: &OpBGP) {
620        for triple in &op.triples {
621            for v in triple.vars() {
622                self.add_var(&v);
623            }
624        }
625    }
626
627    fn visit_triple(&mut self, op: &OpTriple) {
628        for v in op.triple.vars() {
629            self.add_var(&v);
630        }
631    }
632
633    fn visit_project(&mut self, op: &OpProject) {
634        for v in &op.vars {
635            self.add_var(v);
636        }
637    }
638
639    fn visit_group(&mut self, op: &OpGroup) {
640        for v in &op.group_vars {
641            self.add_var(v);
642        }
643        for (v, _) in &op.aggregates {
644            self.add_var(v);
645        }
646    }
647
648    fn visit_extend(&mut self, op: &OpExtend) {
649        self.add_var(&op.var);
650    }
651
652    fn visit_table(&mut self, op: &OpTable) {
653        for v in &op.vars {
654            self.add_var(v);
655        }
656    }
657}
658
659/// Op tree printer visitor
660#[derive(Debug)]
661pub struct OpPrinter {
662    indent: usize,
663    output: String,
664}
665
666impl OpPrinter {
667    /// Create new printer
668    pub fn new() -> Self {
669        Self {
670            indent: 0,
671            output: String::new(),
672        }
673    }
674
675    /// Print op tree to string
676    pub fn print(op: &Op) -> String {
677        let mut printer = Self::new();
678        printer.print_op(op);
679        printer.output
680    }
681
682    fn print_op(&mut self, op: &Op) {
683        let indent_str = "  ".repeat(self.indent);
684
685        match op {
686            Op::BGP(o) => {
687                self.output.push_str(&format!("{}BGP\n", indent_str));
688                self.indent += 1;
689                for triple in &o.triples {
690                    self.output
691                        .push_str(&format!("{}  {}\n", indent_str, triple));
692                }
693                self.indent -= 1;
694            }
695            Op::Triple(o) => {
696                self.output
697                    .push_str(&format!("{}Triple({})\n", indent_str, o.triple));
698            }
699            Op::Join(o) => {
700                self.output.push_str(&format!("{}Join\n", indent_str));
701                self.indent += 1;
702                self.print_op(&o.left);
703                self.print_op(&o.right);
704                self.indent -= 1;
705            }
706            Op::LeftJoin(o) => {
707                self.output.push_str(&format!("{}LeftJoin\n", indent_str));
708                self.indent += 1;
709                self.print_op(&o.left);
710                self.print_op(&o.right);
711                self.indent -= 1;
712            }
713            Op::Filter(o) => {
714                self.output.push_str(&format!("{}Filter\n", indent_str));
715                self.indent += 1;
716                self.print_op(&o.sub_op);
717                self.indent -= 1;
718            }
719            Op::Union(o) => {
720                self.output.push_str(&format!("{}Union\n", indent_str));
721                self.indent += 1;
722                self.print_op(&o.left);
723                self.print_op(&o.right);
724                self.indent -= 1;
725            }
726            Op::Project(o) => {
727                let vars: Vec<String> = o.vars.iter().map(|v| format!("{}", v)).collect();
728                self.output
729                    .push_str(&format!("{}Project({})\n", indent_str, vars.join(", ")));
730                self.indent += 1;
731                self.print_op(&o.sub_op);
732                self.indent -= 1;
733            }
734            Op::Distinct(o) => {
735                self.output.push_str(&format!("{}Distinct\n", indent_str));
736                self.indent += 1;
737                self.print_op(&o.sub_op);
738                self.indent -= 1;
739            }
740            Op::Reduced(o) => {
741                self.output.push_str(&format!("{}Reduced\n", indent_str));
742                self.indent += 1;
743                self.print_op(&o.sub_op);
744                self.indent -= 1;
745            }
746            Op::Slice(o) => {
747                self.output.push_str(&format!(
748                    "{}Slice(offset={}, limit={:?})\n",
749                    indent_str, o.offset, o.limit
750                ));
751                self.indent += 1;
752                self.print_op(&o.sub_op);
753                self.indent -= 1;
754            }
755            Op::Order(o) => {
756                self.output.push_str(&format!("{}Order\n", indent_str));
757                self.indent += 1;
758                self.print_op(&o.sub_op);
759                self.indent -= 1;
760            }
761            Op::Group(o) => {
762                let vars: Vec<String> = o.group_vars.iter().map(|v| format!("{}", v)).collect();
763                self.output
764                    .push_str(&format!("{}Group({})\n", indent_str, vars.join(", ")));
765                self.indent += 1;
766                self.print_op(&o.sub_op);
767                self.indent -= 1;
768            }
769            Op::Extend(o) => {
770                self.output
771                    .push_str(&format!("{}Extend({})\n", indent_str, o.var));
772                self.indent += 1;
773                self.print_op(&o.sub_op);
774                self.indent -= 1;
775            }
776            Op::Minus(o) => {
777                self.output.push_str(&format!("{}Minus\n", indent_str));
778                self.indent += 1;
779                self.print_op(&o.left);
780                self.print_op(&o.right);
781                self.indent -= 1;
782            }
783            Op::RightJoin(o) => {
784                self.output.push_str(&format!("{}RightJoin\n", indent_str));
785                self.indent += 1;
786                self.print_op(&o.left);
787                self.print_op(&o.right);
788                self.indent -= 1;
789            }
790            Op::CrossJoin(o) => {
791                self.output.push_str(&format!("{}CrossJoin\n", indent_str));
792                self.indent += 1;
793                self.print_op(&o.left);
794                self.print_op(&o.right);
795                self.indent -= 1;
796            }
797            Op::Intersect(o) => {
798                self.output.push_str(&format!("{}Intersect\n", indent_str));
799                self.indent += 1;
800                self.print_op(&o.left);
801                self.print_op(&o.right);
802                self.indent -= 1;
803            }
804            Op::Table(o) => {
805                self.output.push_str(&format!(
806                    "{}Table({} vars, {} rows)\n",
807                    indent_str,
808                    o.vars.len(),
809                    o.rows.len()
810                ));
811            }
812            Op::Sequence(o) => {
813                self.output.push_str(&format!("{}Sequence\n", indent_str));
814                self.indent += 1;
815                for sub in &o.ops {
816                    self.print_op(sub);
817                }
818                self.indent -= 1;
819            }
820            Op::Disjunction(o) => {
821                self.output
822                    .push_str(&format!("{}Disjunction\n", indent_str));
823                self.indent += 1;
824                for sub in &o.ops {
825                    self.print_op(sub);
826                }
827                self.indent -= 1;
828            }
829            Op::Null(_) => {
830                self.output.push_str(&format!("{}Null\n", indent_str));
831            }
832        }
833    }
834}
835
836impl Default for OpPrinter {
837    fn default() -> Self {
838        Self::new()
839    }
840}
841
842/// Op statistics visitor
843#[derive(Debug, Default)]
844pub struct OpStats {
845    pub bgp_count: usize,
846    pub triple_count: usize,
847    pub join_count: usize,
848    pub filter_count: usize,
849    pub union_count: usize,
850    pub total_ops: usize,
851}
852
853impl OpStats {
854    /// Create new stats collector
855    pub fn new() -> Self {
856        Self::default()
857    }
858
859    /// Collect stats from op
860    pub fn collect(op: &Op) -> Self {
861        let mut stats = Self::new();
862        walk_op(&mut stats, op);
863        stats
864    }
865}
866
867impl OpVisitor for OpStats {
868    fn visit_bgp(&mut self, op: &OpBGP) {
869        self.bgp_count += 1;
870        self.triple_count += op.triples.len();
871        self.total_ops += 1;
872    }
873
874    fn visit_triple(&mut self, _op: &OpTriple) {
875        self.triple_count += 1;
876        self.total_ops += 1;
877    }
878
879    fn visit_join(&mut self, _op: &OpJoin) {
880        self.join_count += 1;
881        self.total_ops += 1;
882    }
883
884    fn visit_filter(&mut self, _op: &OpFilter) {
885        self.filter_count += 1;
886        self.total_ops += 1;
887    }
888
889    fn visit_union(&mut self, _op: &OpUnion) {
890        self.union_count += 1;
891        self.total_ops += 1;
892    }
893
894    fn visit_left_join(&mut self, _op: &OpLeftJoin) {
895        self.total_ops += 1;
896    }
897
898    fn visit_project(&mut self, _op: &OpProject) {
899        self.total_ops += 1;
900    }
901
902    fn visit_distinct(&mut self, _op: &OpDistinct) {
903        self.total_ops += 1;
904    }
905
906    fn visit_reduced(&mut self, _op: &OpReduced) {
907        self.total_ops += 1;
908    }
909
910    fn visit_slice(&mut self, _op: &OpSlice) {
911        self.total_ops += 1;
912    }
913
914    fn visit_order(&mut self, _op: &OpOrder) {
915        self.total_ops += 1;
916    }
917
918    fn visit_group(&mut self, _op: &OpGroup) {
919        self.total_ops += 1;
920    }
921
922    fn visit_extend(&mut self, _op: &OpExtend) {
923        self.total_ops += 1;
924    }
925
926    fn visit_minus(&mut self, _op: &OpMinus) {
927        self.total_ops += 1;
928    }
929
930    fn visit_table(&mut self, _op: &OpTable) {
931        self.total_ops += 1;
932    }
933
934    fn visit_sequence(&mut self, _op: &OpSequence) {
935        self.total_ops += 1;
936    }
937
938    fn visit_disjunction(&mut self, _op: &OpDisjunction) {
939        self.total_ops += 1;
940    }
941
942    fn visit_null(&mut self, _op: &OpNull) {
943        self.total_ops += 1;
944    }
945}
946
947#[cfg(test)]
948mod tests {
949    use super::super::binding::Value;
950    use super::*;
951
952    fn make_bgp() -> OpBGP {
953        let mut bgp = OpBGP::new();
954        bgp.add(Triple::new(
955            Pattern::Var(Var::new("s")),
956            Pattern::Uri("knows".to_string()),
957            Pattern::Var(Var::new("o")),
958        ));
959        bgp
960    }
961
962    #[test]
963    fn test_walk_op() {
964        let bgp = make_bgp();
965        let filter = Op::Filter(OpFilter::new(FilterExpr::True, Op::BGP(bgp)));
966
967        let mut stats = OpStats::new();
968        walk_op(&mut stats, &filter);
969
970        assert_eq!(stats.bgp_count, 1);
971        assert_eq!(stats.filter_count, 1);
972        assert_eq!(stats.total_ops, 2);
973    }
974
975    #[test]
976    fn test_transform_copy() {
977        let bgp = make_bgp();
978        let op = Op::BGP(bgp);
979
980        let mut copy = TransformCopy::new();
981        let result = transform_op(&mut copy, op.clone());
982
983        // Should produce equivalent tree
984        assert!(matches!(result, Op::BGP(_)));
985    }
986
987    #[test]
988    fn test_var_collector() {
989        let bgp = make_bgp();
990        let op = Op::BGP(bgp);
991
992        let vars = VarCollector::collect(&op);
993        assert_eq!(vars.len(), 2);
994        assert!(vars.contains(&Var::new("s")));
995        assert!(vars.contains(&Var::new("o")));
996    }
997
998    #[test]
999    fn test_op_printer() {
1000        let bgp = make_bgp();
1001        let filter = Op::Filter(OpFilter::new(
1002            FilterExpr::True,
1003            Op::Project(OpProject::new(vec![Var::new("s")], Op::BGP(bgp))),
1004        ));
1005
1006        let output = OpPrinter::print(&filter);
1007        assert!(output.contains("Filter"));
1008        assert!(output.contains("Project"));
1009        assert!(output.contains("BGP"));
1010    }
1011
1012    #[test]
1013    fn test_op_stats() {
1014        let bgp1 = make_bgp();
1015        let bgp2 = make_bgp();
1016
1017        let join = Op::Join(OpJoin::new(Op::BGP(bgp1), Op::BGP(bgp2)));
1018
1019        let stats = OpStats::collect(&join);
1020        assert_eq!(stats.bgp_count, 2);
1021        assert_eq!(stats.join_count, 1);
1022        assert_eq!(stats.triple_count, 2);
1023    }
1024
1025    #[test]
1026    fn test_push_filter_transform() {
1027        // Filter(Join(BGP1, BGP2))
1028        // Should push applicable filters down
1029        let mut bgp1 = OpBGP::new();
1030        bgp1.add(Triple::new(
1031            Pattern::Var(Var::new("s")),
1032            Pattern::Uri("name".to_string()),
1033            Pattern::Var(Var::new("name")),
1034        ));
1035
1036        let mut bgp2 = OpBGP::new();
1037        bgp2.add(Triple::new(
1038            Pattern::Var(Var::new("s")),
1039            Pattern::Uri("age".to_string()),
1040            Pattern::Var(Var::new("age")),
1041        ));
1042
1043        // Filter on "age" variable (only in bgp2)
1044        let filter = FilterExpr::Gt(
1045            ExprTerm::Var(Var::new("age")),
1046            ExprTerm::Const(Value::Integer(18)),
1047        );
1048
1049        let op = Op::Filter(OpFilter::new(
1050            filter,
1051            Op::Join(OpJoin::new(Op::BGP(bgp1), Op::BGP(bgp2))),
1052        ));
1053
1054        let mut transform = TransformPushFilter::new();
1055        let result = transform_op(&mut transform, op);
1056
1057        // The filter should still be in the tree
1058        let stats = OpStats::collect(&result);
1059        assert_eq!(stats.filter_count, 1);
1060    }
1061}