1use super::registry::FieldRegistry;
39use super::types::{Expr, Query};
40
41#[derive(Debug)]
46pub struct Optimizer {
47 registry: FieldRegistry,
49}
50
51impl Optimizer {
52 #[must_use]
54 pub fn new(registry: FieldRegistry) -> Self {
55 Self { registry }
56 }
57
58 #[must_use]
62 pub fn optimize_query(&self, query: Query) -> Query {
63 Query {
64 root: self.optimize(query.root),
65 span: query.span,
66 }
67 }
68
69 #[must_use]
76 pub fn optimize(&self, expr: Expr) -> Expr {
77 match expr {
78 Expr::And(operands) => {
79 let optimized: Vec<Expr> = operands.into_iter().map(|e| self.optimize(e)).collect();
81
82 let flattened = Self::flatten_and(optimized);
84
85 self.reorder_and(flattened)
87 }
88 Expr::Or(operands) => {
89 let optimized: Vec<Expr> = operands.into_iter().map(|e| self.optimize(e)).collect();
91
92 let flattened = Self::flatten_or(optimized);
94
95 Expr::Or(flattened)
97 }
98 Expr::Not(operand) => {
99 Expr::Not(Box::new(self.optimize(*operand)))
101 }
102 Expr::Condition(_) => {
103 expr
105 }
106 Expr::Join(join) => {
107 Expr::Join(crate::query::types::JoinExpr {
109 left: Box::new(self.optimize(*join.left)),
110 edge: join.edge,
111 right: Box::new(self.optimize(*join.right)),
112 span: join.span,
113 })
114 }
115 }
116 }
117
118 fn flatten_and(operands: Vec<Expr>) -> Vec<Expr> {
123 let mut result = Vec::new();
124
125 for operand in operands {
126 match operand {
127 Expr::And(nested) => {
128 result.extend(Self::flatten_and(nested));
130 }
131 other => {
132 result.push(other);
133 }
134 }
135 }
136
137 result
138 }
139
140 fn flatten_or(operands: Vec<Expr>) -> Vec<Expr> {
144 let mut result = Vec::new();
145
146 for operand in operands {
147 match operand {
148 Expr::Or(nested) => {
149 result.extend(Self::flatten_or(nested));
151 }
152 other => {
153 result.push(other);
154 }
155 }
156 }
157
158 result
159 }
160
161 fn reorder_and(&self, operands: Vec<Expr>) -> Expr {
169 if operands.is_empty() {
170 return Expr::And(vec![]);
172 }
173
174 if operands.len() == 1 {
175 return operands.into_iter().next().unwrap();
177 }
178
179 let mut conditions = Vec::new();
181 let mut complex_exprs = Vec::new();
182
183 for operand in operands {
184 match &operand {
185 Expr::Condition(_) => conditions.push(operand),
186 _ => complex_exprs.push(operand),
187 }
188 }
189
190 conditions.sort_by(|a, b| {
192 let (a_indexed, a_selectivity) = self.analyze_operand(a);
193 let (b_indexed, b_selectivity) = self.analyze_operand(b);
194
195 match (a_indexed, b_indexed) {
197 (true, false) => std::cmp::Ordering::Less,
198 (false, true) => std::cmp::Ordering::Greater,
199 _ => {
200 a_selectivity
203 .partial_cmp(&b_selectivity)
204 .unwrap_or(std::cmp::Ordering::Equal)
205 }
206 }
207 });
208
209 let mut result = conditions;
211 result.extend(complex_exprs);
212
213 Expr::And(result)
214 }
215
216 fn analyze_operand(&self, operand: &Expr) -> (bool, f64) {
222 match operand {
223 Expr::Condition(condition) => {
224 let field_name = condition.field.as_str();
225
226 let is_indexed = self
228 .registry
229 .get(field_name)
230 .is_some_and(|desc| desc.indexed);
231
232 let selectivity = Self::estimate_selectivity(field_name);
234
235 (is_indexed, selectivity)
236 }
237 _ => {
238 (false, 0.5)
241 }
242 }
243 }
244
245 fn estimate_selectivity(field: &str) -> f64 {
260 match field {
261 "kind" => 0.1, "name" => 0.01, "path" => 0.3, "lang" => 0.4, "scope" => 0.35, "text" => 0.5, _ => 0.45, }
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275 use crate::query::types::{Condition, Field, Operator, Span, Value};
276 use approx::assert_abs_diff_eq;
277
278 fn make_condition(field: &str, value: &str) -> Expr {
279 Expr::Condition(Condition {
280 field: Field::new(field),
281 operator: Operator::Equal,
282 value: Value::String(value.to_string()),
283 span: Span::default(),
284 })
285 }
286
287 #[test]
288 fn test_optimize_single_condition() {
289 let registry = FieldRegistry::with_core_fields();
290 let optimizer = Optimizer::new(registry);
291
292 let expr = make_condition("kind", "function");
293 let rewritten_expr = optimizer.optimize(expr.clone());
294
295 assert_eq!(rewritten_expr, expr);
297 }
298
299 #[test]
300 fn test_flatten_nested_and() {
301 let registry = FieldRegistry::with_core_fields();
302 let optimizer = Optimizer::new(registry);
303
304 let a = make_condition("kind", "function");
306 let b = make_condition("name", "foo");
307 let c = make_condition("lang", "rust");
308
309 let nested_and = Expr::And(vec![Expr::And(vec![a.clone(), b.clone()]), c.clone()]);
310
311 let rewritten_expr = optimizer.optimize(nested_and);
312
313 match rewritten_expr {
315 Expr::And(operands) => {
316 assert_eq!(operands.len(), 3);
317 }
318 _ => panic!("Expected And expression"),
319 }
320 }
321
322 #[test]
323 fn test_flatten_deeply_nested_and() {
324 let registry = FieldRegistry::with_core_fields();
325 let optimizer = Optimizer::new(registry);
326
327 let a = make_condition("kind", "function");
329 let b = make_condition("name", "foo");
330 let c = make_condition("lang", "rust");
331 let d = make_condition("path", "src/main.rs");
332
333 let deeply_nested = Expr::And(vec![
334 Expr::And(vec![Expr::And(vec![a.clone(), b.clone()]), c.clone()]),
335 d.clone(),
336 ]);
337
338 let rewritten_expr = optimizer.optimize(deeply_nested);
339
340 match rewritten_expr {
342 Expr::And(operands) => {
343 assert_eq!(operands.len(), 4);
344 }
345 _ => panic!("Expected And expression"),
346 }
347 }
348
349 #[test]
350 fn test_flatten_nested_or() {
351 let registry = FieldRegistry::with_core_fields();
352 let optimizer = Optimizer::new(registry);
353
354 let a = make_condition("kind", "function");
356 let b = make_condition("kind", "method");
357 let c = make_condition("kind", "class");
358
359 let nested_or = Expr::Or(vec![Expr::Or(vec![a.clone(), b.clone()]), c.clone()]);
360
361 let rewritten_expr = optimizer.optimize(nested_or);
362
363 match rewritten_expr {
365 Expr::Or(operands) => {
366 assert_eq!(operands.len(), 3);
367 }
368 _ => panic!("Expected Or expression"),
369 }
370 }
371
372 #[test]
373 fn test_reorder_for_index() {
374 let registry = FieldRegistry::with_core_fields();
375 let optimizer = Optimizer::new(registry);
376
377 let text_cond = make_condition("text", "TODO");
379 let kind_cond = make_condition("kind", "function");
380
381 let expr = Expr::And(vec![text_cond.clone(), kind_cond.clone()]);
382 let rewritten_expr = optimizer.optimize(expr);
383
384 match rewritten_expr {
386 Expr::And(operands) => {
387 assert_eq!(operands.len(), 2);
388 match &operands[0] {
390 Expr::Condition(cond) => {
391 assert_eq!(cond.field.as_str(), "kind");
392 }
393 _ => panic!("Expected Condition"),
394 }
395 match &operands[1] {
397 Expr::Condition(cond) => {
398 assert_eq!(cond.field.as_str(), "text");
399 }
400 _ => panic!("Expected Condition"),
401 }
402 }
403 _ => panic!("Expected And expression"),
404 }
405 }
406
407 #[test]
408 fn test_reorder_by_selectivity() {
409 let registry = FieldRegistry::with_core_fields();
410 let optimizer = Optimizer::new(registry);
411
412 let lang_cond = make_condition("lang", "rust");
415 let name_cond = make_condition("name", "foo");
416
417 let expr = Expr::And(vec![lang_cond.clone(), name_cond.clone()]);
418 let rewritten_expr = optimizer.optimize(expr);
419
420 match rewritten_expr {
422 Expr::And(operands) => {
423 assert_eq!(operands.len(), 2);
424 match &operands[0] {
426 Expr::Condition(cond) => {
427 assert_eq!(cond.field.as_str(), "name");
428 }
429 _ => panic!("Expected Condition"),
430 }
431 }
432 _ => panic!("Expected And expression"),
433 }
434 }
435
436 #[test]
437 fn test_reorder_mixed_indexed_and_selectivity() {
438 let registry = FieldRegistry::with_core_fields();
439 let optimizer = Optimizer::new(registry);
440
441 let path_cond = make_condition("path", "src/**/*.rs");
443 let kind_cond = make_condition("kind", "function");
444 let text_cond = make_condition("text", "TODO");
445
446 let expr = Expr::And(vec![
447 path_cond.clone(),
448 kind_cond.clone(),
449 text_cond.clone(),
450 ]);
451 let rewritten_expr = optimizer.optimize(expr);
452
453 match rewritten_expr {
455 Expr::And(operands) => {
456 assert_eq!(operands.len(), 3);
457
458 match &operands[0] {
460 Expr::Condition(cond) => {
461 assert_eq!(cond.field.as_str(), "kind");
462 }
463 _ => panic!("Expected Condition"),
464 }
465
466 match &operands[1] {
468 Expr::Condition(cond) => {
469 assert_eq!(cond.field.as_str(), "path");
470 }
471 _ => panic!("Expected Condition"),
472 }
473
474 match &operands[2] {
476 Expr::Condition(cond) => {
477 assert_eq!(cond.field.as_str(), "text");
478 }
479 _ => panic!("Expected Condition"),
480 }
481 }
482 _ => panic!("Expected And expression"),
483 }
484 }
485
486 #[test]
487 fn test_preserve_or_order() {
488 let registry = FieldRegistry::with_core_fields();
489 let optimizer = Optimizer::new(registry);
490
491 let text_cond = make_condition("text", "TODO");
493 let kind_cond = make_condition("kind", "function");
494
495 let expr = Expr::Or(vec![text_cond.clone(), kind_cond.clone()]);
496 let rewritten_expr = optimizer.optimize(expr);
497
498 match rewritten_expr {
500 Expr::Or(operands) => {
501 assert_eq!(operands.len(), 2);
502 match &operands[0] {
503 Expr::Condition(cond) => {
504 assert_eq!(cond.field.as_str(), "text");
505 }
506 _ => panic!("Expected Condition"),
507 }
508 }
509 _ => panic!("Expected Or expression"),
510 }
511 }
512
513 #[test]
514 fn test_optimize_not() {
515 let registry = FieldRegistry::with_core_fields();
516 let optimizer = Optimizer::new(registry);
517
518 let kind_cond = make_condition("kind", "function");
519 let expr = Expr::Not(Box::new(kind_cond.clone()));
520
521 let rewritten_expr = optimizer.optimize(expr);
522
523 match rewritten_expr {
525 Expr::Not(inner) => {
526 assert_eq!(*inner, kind_cond);
527 }
528 _ => panic!("Expected Not expression"),
529 }
530 }
531
532 #[test]
533 fn test_optimize_complex_nested() {
534 let registry = FieldRegistry::with_core_fields();
535 let optimizer = Optimizer::new(registry);
536
537 let text_cond = make_condition("text", "TODO");
539 let name_cond = make_condition("name", "foo");
540 let kind_cond = make_condition("kind", "function");
541
542 let or_expr = Expr::Or(vec![text_cond.clone(), name_cond.clone()]);
543 let expr = Expr::And(vec![or_expr.clone(), kind_cond.clone()]);
544
545 let rewritten_expr = optimizer.optimize(expr);
546
547 match rewritten_expr {
549 Expr::And(operands) => {
550 assert_eq!(operands.len(), 2);
551
552 match &operands[0] {
554 Expr::Condition(cond) => {
555 assert_eq!(cond.field.as_str(), "kind");
556 }
557 _ => panic!("Expected Condition"),
558 }
559
560 match &operands[1] {
562 Expr::Or(_) => {
563 }
565 _ => panic!("Expected Or expression"),
566 }
567 }
568 _ => panic!("Expected And expression"),
569 }
570 }
571
572 #[test]
573 fn test_optimize_query() {
574 let registry = FieldRegistry::with_core_fields();
575 let optimizer = Optimizer::new(registry);
576
577 let text_cond = make_condition("text", "TODO");
578 let kind_cond = make_condition("kind", "function");
579
580 let query = Query {
581 root: Expr::And(vec![text_cond, kind_cond]),
582 span: Span::default(),
583 };
584
585 let rewritten_query = optimizer.optimize_query(query);
586
587 match rewritten_query.root {
589 Expr::And(operands) => match &operands[0] {
590 Expr::Condition(cond) => {
591 assert_eq!(cond.field.as_str(), "kind");
592 }
593 _ => panic!("Expected Condition"),
594 },
595 _ => panic!("Expected And expression"),
596 }
597 }
598
599 #[test]
600 fn test_analyze_operand_indexed_field() {
601 let registry = FieldRegistry::with_core_fields();
602 let optimizer = Optimizer::new(registry);
603
604 let kind_cond = make_condition("kind", "function");
605 let (indexed, selectivity) = optimizer.analyze_operand(&kind_cond);
606
607 assert!(indexed);
608 assert_abs_diff_eq!(selectivity, 0.1, epsilon = 1e-10); }
610
611 #[test]
612 fn test_analyze_operand_non_indexed_field() {
613 let registry = FieldRegistry::with_core_fields();
614 let optimizer = Optimizer::new(registry);
615
616 let text_cond = make_condition("text", "TODO");
617 let (indexed, selectivity) = optimizer.analyze_operand(&text_cond);
618
619 assert!(!indexed);
620 assert_abs_diff_eq!(selectivity, 0.5, epsilon = 1e-10); }
622
623 #[test]
624 fn test_analyze_operand_complex_expression() {
625 let registry = FieldRegistry::with_core_fields();
626 let optimizer = Optimizer::new(registry);
627
628 let a = make_condition("kind", "function");
629 let b = make_condition("name", "foo");
630 let or_expr = Expr::Or(vec![a, b]);
631
632 let (indexed, selectivity) = optimizer.analyze_operand(&or_expr);
633
634 assert!(!indexed);
636 assert_abs_diff_eq!(selectivity, 0.5, epsilon = 1e-10); }
638
639 #[test]
640 fn test_estimate_selectivity_known_fields() {
641 assert_abs_diff_eq!(
642 Optimizer::estimate_selectivity("kind"),
643 0.1,
644 epsilon = 1e-10
645 );
646 assert_abs_diff_eq!(
647 Optimizer::estimate_selectivity("name"),
648 0.01,
649 epsilon = 1e-10
650 );
651 assert_abs_diff_eq!(
652 Optimizer::estimate_selectivity("path"),
653 0.3,
654 epsilon = 1e-10
655 );
656 assert_abs_diff_eq!(
657 Optimizer::estimate_selectivity("lang"),
658 0.4,
659 epsilon = 1e-10
660 );
661 assert_abs_diff_eq!(
662 Optimizer::estimate_selectivity("scope"),
663 0.35,
664 epsilon = 1e-10
665 );
666 assert_abs_diff_eq!(
667 Optimizer::estimate_selectivity("text"),
668 0.5,
669 epsilon = 1e-10
670 );
671 }
672
673 #[test]
674 fn test_estimate_selectivity_unknown_field() {
675 assert_abs_diff_eq!(
677 Optimizer::estimate_selectivity("unknown_field"),
678 0.45,
679 epsilon = 1e-10
680 );
681 }
682
683 #[test]
684 fn test_single_and_operand_unwrapped() {
685 let registry = FieldRegistry::with_core_fields();
686 let optimizer = Optimizer::new(registry);
687
688 let kind_cond = make_condition("kind", "function");
690 let expr = Expr::And(vec![kind_cond.clone()]);
691
692 let rewritten_expr = optimizer.optimize(expr);
693
694 assert_eq!(rewritten_expr, kind_cond);
696 }
697
698 #[test]
699 fn test_empty_and() {
700 let registry = FieldRegistry::with_core_fields();
701 let optimizer = Optimizer::new(registry);
702
703 let expr = Expr::And(vec![]);
704 let rewritten_expr = optimizer.optimize(expr);
705
706 match rewritten_expr {
708 Expr::And(operands) => {
709 assert_eq!(operands.len(), 0);
710 }
711 _ => panic!("Expected And expression"),
712 }
713 }
714
715 #[test]
716 fn test_flatten_multiple_and_levels() {
717 let registry = FieldRegistry::with_core_fields();
718 let optimizer = Optimizer::new(registry);
719
720 let a = make_condition("kind", "function");
721 let b = make_condition("name", "foo");
722 let c = make_condition("lang", "rust");
723 let d = make_condition("path", "src/main.rs");
724
725 let expr = Expr::And(vec![
727 Expr::And(vec![a.clone(), b.clone()]),
728 Expr::And(vec![c.clone(), d.clone()]),
729 ]);
730
731 let rewritten_expr = optimizer.optimize(expr);
732
733 match rewritten_expr {
735 Expr::And(operands) => {
736 assert_eq!(operands.len(), 4, "Should flatten to 4 operands");
737
738 let field_names: Vec<_> = operands
740 .iter()
741 .filter_map(|op| match op {
742 Expr::Condition(cond) => Some(cond.field.as_str()),
743 _ => None,
744 })
745 .collect();
746
747 assert_eq!(field_names.len(), 4);
748 assert!(field_names.contains(&"kind"));
749 assert!(field_names.contains(&"name"));
750 assert!(field_names.contains(&"lang"));
751 assert!(field_names.contains(&"path"));
752 }
753 _ => panic!("Expected And expression"),
754 }
755 }
756
757 #[test]
758 fn test_flatten_with_mixed_nesting() {
759 let registry = FieldRegistry::with_core_fields();
760 let optimizer = Optimizer::new(registry);
761
762 let a = make_condition("kind", "function");
763 let b = make_condition("name", "foo");
764 let c = make_condition("lang", "rust");
765 let d = make_condition("path", "src/main.rs");
766
767 let expr = Expr::And(vec![
769 a.clone(),
770 Expr::And(vec![b.clone(), Expr::And(vec![c.clone(), d.clone()])]),
771 ]);
772
773 let rewritten_expr = optimizer.optimize(expr);
774
775 match rewritten_expr {
777 Expr::And(operands) => {
778 assert_eq!(operands.len(), 4, "Should flatten to 4 operands");
779
780 let field_names: Vec<_> = operands
782 .iter()
783 .filter_map(|op| match op {
784 Expr::Condition(cond) => Some(cond.field.as_str()),
785 _ => None,
786 })
787 .collect();
788
789 assert_eq!(field_names.len(), 4);
790 assert!(field_names.contains(&"kind"));
791 assert!(field_names.contains(&"name"));
792 assert!(field_names.contains(&"lang"));
793 assert!(field_names.contains(&"path"));
794 }
795 _ => panic!("Expected And expression"),
796 }
797 }
798}