use super::registry::FieldRegistry;
use super::types::{Expr, Query};
#[derive(Debug)]
pub struct Optimizer {
registry: FieldRegistry,
}
impl Optimizer {
#[must_use]
pub fn new(registry: FieldRegistry) -> Self {
Self { registry }
}
#[must_use]
pub fn optimize_query(&self, query: Query) -> Query {
Query {
root: self.optimize(query.root),
span: query.span,
}
}
#[must_use]
pub fn optimize(&self, expr: Expr) -> Expr {
match expr {
Expr::And(operands) => {
let optimized: Vec<Expr> = operands.into_iter().map(|e| self.optimize(e)).collect();
let flattened = Self::flatten_and(optimized);
self.reorder_and(flattened)
}
Expr::Or(operands) => {
let optimized: Vec<Expr> = operands.into_iter().map(|e| self.optimize(e)).collect();
let flattened = Self::flatten_or(optimized);
Expr::Or(flattened)
}
Expr::Not(operand) => {
Expr::Not(Box::new(self.optimize(*operand)))
}
Expr::Condition(_) => {
expr
}
Expr::Join(join) => {
Expr::Join(crate::query::types::JoinExpr {
left: Box::new(self.optimize(*join.left)),
edge: join.edge,
right: Box::new(self.optimize(*join.right)),
span: join.span,
})
}
}
}
fn flatten_and(operands: Vec<Expr>) -> Vec<Expr> {
let mut result = Vec::new();
for operand in operands {
match operand {
Expr::And(nested) => {
result.extend(Self::flatten_and(nested));
}
other => {
result.push(other);
}
}
}
result
}
fn flatten_or(operands: Vec<Expr>) -> Vec<Expr> {
let mut result = Vec::new();
for operand in operands {
match operand {
Expr::Or(nested) => {
result.extend(Self::flatten_or(nested));
}
other => {
result.push(other);
}
}
}
result
}
fn reorder_and(&self, operands: Vec<Expr>) -> Expr {
if operands.is_empty() {
return Expr::And(vec![]);
}
if operands.len() == 1 {
return operands.into_iter().next().unwrap();
}
let mut conditions = Vec::new();
let mut complex_exprs = Vec::new();
for operand in operands {
match &operand {
Expr::Condition(_) => conditions.push(operand),
_ => complex_exprs.push(operand),
}
}
conditions.sort_by(|a, b| {
let (a_indexed, a_selectivity) = self.analyze_operand(a);
let (b_indexed, b_selectivity) = self.analyze_operand(b);
match (a_indexed, b_indexed) {
(true, false) => std::cmp::Ordering::Less,
(false, true) => std::cmp::Ordering::Greater,
_ => {
a_selectivity
.partial_cmp(&b_selectivity)
.unwrap_or(std::cmp::Ordering::Equal)
}
}
});
let mut result = conditions;
result.extend(complex_exprs);
Expr::And(result)
}
fn analyze_operand(&self, operand: &Expr) -> (bool, f64) {
match operand {
Expr::Condition(condition) => {
let field_name = condition.field.as_str();
let is_indexed = self
.registry
.get(field_name)
.is_some_and(|desc| desc.indexed);
let selectivity = Self::estimate_selectivity(field_name);
(is_indexed, selectivity)
}
_ => {
(false, 0.5)
}
}
}
fn estimate_selectivity(field: &str) -> f64 {
match field {
"kind" => 0.1, "name" => 0.01, "path" => 0.3, "lang" => 0.4, "scope" => 0.35, "text" => 0.5, _ => 0.45, }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::query::types::{Condition, Field, Operator, Span, Value};
use approx::assert_abs_diff_eq;
fn make_condition(field: &str, value: &str) -> Expr {
Expr::Condition(Condition {
field: Field::new(field),
operator: Operator::Equal,
value: Value::String(value.to_string()),
span: Span::default(),
})
}
#[test]
fn test_optimize_single_condition() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let expr = make_condition("kind", "function");
let rewritten_expr = optimizer.optimize(expr.clone());
assert_eq!(rewritten_expr, expr);
}
#[test]
fn test_flatten_nested_and() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let a = make_condition("kind", "function");
let b = make_condition("name", "foo");
let c = make_condition("lang", "rust");
let nested_and = Expr::And(vec![Expr::And(vec![a.clone(), b.clone()]), c.clone()]);
let rewritten_expr = optimizer.optimize(nested_and);
match rewritten_expr {
Expr::And(operands) => {
assert_eq!(operands.len(), 3);
}
_ => panic!("Expected And expression"),
}
}
#[test]
fn test_flatten_deeply_nested_and() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let a = make_condition("kind", "function");
let b = make_condition("name", "foo");
let c = make_condition("lang", "rust");
let d = make_condition("path", "src/main.rs");
let deeply_nested = Expr::And(vec![
Expr::And(vec![Expr::And(vec![a.clone(), b.clone()]), c.clone()]),
d.clone(),
]);
let rewritten_expr = optimizer.optimize(deeply_nested);
match rewritten_expr {
Expr::And(operands) => {
assert_eq!(operands.len(), 4);
}
_ => panic!("Expected And expression"),
}
}
#[test]
fn test_flatten_nested_or() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let a = make_condition("kind", "function");
let b = make_condition("kind", "method");
let c = make_condition("kind", "class");
let nested_or = Expr::Or(vec![Expr::Or(vec![a.clone(), b.clone()]), c.clone()]);
let rewritten_expr = optimizer.optimize(nested_or);
match rewritten_expr {
Expr::Or(operands) => {
assert_eq!(operands.len(), 3);
}
_ => panic!("Expected Or expression"),
}
}
#[test]
fn test_reorder_for_index() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let text_cond = make_condition("text", "TODO");
let kind_cond = make_condition("kind", "function");
let expr = Expr::And(vec![text_cond.clone(), kind_cond.clone()]);
let rewritten_expr = optimizer.optimize(expr);
match rewritten_expr {
Expr::And(operands) => {
assert_eq!(operands.len(), 2);
match &operands[0] {
Expr::Condition(cond) => {
assert_eq!(cond.field.as_str(), "kind");
}
_ => panic!("Expected Condition"),
}
match &operands[1] {
Expr::Condition(cond) => {
assert_eq!(cond.field.as_str(), "text");
}
_ => panic!("Expected Condition"),
}
}
_ => panic!("Expected And expression"),
}
}
#[test]
fn test_reorder_by_selectivity() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let lang_cond = make_condition("lang", "rust");
let name_cond = make_condition("name", "foo");
let expr = Expr::And(vec![lang_cond.clone(), name_cond.clone()]);
let rewritten_expr = optimizer.optimize(expr);
match rewritten_expr {
Expr::And(operands) => {
assert_eq!(operands.len(), 2);
match &operands[0] {
Expr::Condition(cond) => {
assert_eq!(cond.field.as_str(), "name");
}
_ => panic!("Expected Condition"),
}
}
_ => panic!("Expected And expression"),
}
}
#[test]
fn test_reorder_mixed_indexed_and_selectivity() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let path_cond = make_condition("path", "src/**/*.rs");
let kind_cond = make_condition("kind", "function");
let text_cond = make_condition("text", "TODO");
let expr = Expr::And(vec![
path_cond.clone(),
kind_cond.clone(),
text_cond.clone(),
]);
let rewritten_expr = optimizer.optimize(expr);
match rewritten_expr {
Expr::And(operands) => {
assert_eq!(operands.len(), 3);
match &operands[0] {
Expr::Condition(cond) => {
assert_eq!(cond.field.as_str(), "kind");
}
_ => panic!("Expected Condition"),
}
match &operands[1] {
Expr::Condition(cond) => {
assert_eq!(cond.field.as_str(), "path");
}
_ => panic!("Expected Condition"),
}
match &operands[2] {
Expr::Condition(cond) => {
assert_eq!(cond.field.as_str(), "text");
}
_ => panic!("Expected Condition"),
}
}
_ => panic!("Expected And expression"),
}
}
#[test]
fn test_preserve_or_order() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let text_cond = make_condition("text", "TODO");
let kind_cond = make_condition("kind", "function");
let expr = Expr::Or(vec![text_cond.clone(), kind_cond.clone()]);
let rewritten_expr = optimizer.optimize(expr);
match rewritten_expr {
Expr::Or(operands) => {
assert_eq!(operands.len(), 2);
match &operands[0] {
Expr::Condition(cond) => {
assert_eq!(cond.field.as_str(), "text");
}
_ => panic!("Expected Condition"),
}
}
_ => panic!("Expected Or expression"),
}
}
#[test]
fn test_optimize_not() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let kind_cond = make_condition("kind", "function");
let expr = Expr::Not(Box::new(kind_cond.clone()));
let rewritten_expr = optimizer.optimize(expr);
match rewritten_expr {
Expr::Not(inner) => {
assert_eq!(*inner, kind_cond);
}
_ => panic!("Expected Not expression"),
}
}
#[test]
fn test_optimize_complex_nested() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let text_cond = make_condition("text", "TODO");
let name_cond = make_condition("name", "foo");
let kind_cond = make_condition("kind", "function");
let or_expr = Expr::Or(vec![text_cond.clone(), name_cond.clone()]);
let expr = Expr::And(vec![or_expr.clone(), kind_cond.clone()]);
let rewritten_expr = optimizer.optimize(expr);
match rewritten_expr {
Expr::And(operands) => {
assert_eq!(operands.len(), 2);
match &operands[0] {
Expr::Condition(cond) => {
assert_eq!(cond.field.as_str(), "kind");
}
_ => panic!("Expected Condition"),
}
match &operands[1] {
Expr::Or(_) => {
}
_ => panic!("Expected Or expression"),
}
}
_ => panic!("Expected And expression"),
}
}
#[test]
fn test_optimize_query() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let text_cond = make_condition("text", "TODO");
let kind_cond = make_condition("kind", "function");
let query = Query {
root: Expr::And(vec![text_cond, kind_cond]),
span: Span::default(),
};
let rewritten_query = optimizer.optimize_query(query);
match rewritten_query.root {
Expr::And(operands) => match &operands[0] {
Expr::Condition(cond) => {
assert_eq!(cond.field.as_str(), "kind");
}
_ => panic!("Expected Condition"),
},
_ => panic!("Expected And expression"),
}
}
#[test]
fn test_analyze_operand_indexed_field() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let kind_cond = make_condition("kind", "function");
let (indexed, selectivity) = optimizer.analyze_operand(&kind_cond);
assert!(indexed);
assert_abs_diff_eq!(selectivity, 0.1, epsilon = 1e-10); }
#[test]
fn test_analyze_operand_non_indexed_field() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let text_cond = make_condition("text", "TODO");
let (indexed, selectivity) = optimizer.analyze_operand(&text_cond);
assert!(!indexed);
assert_abs_diff_eq!(selectivity, 0.5, epsilon = 1e-10); }
#[test]
fn test_analyze_operand_complex_expression() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let a = make_condition("kind", "function");
let b = make_condition("name", "foo");
let or_expr = Expr::Or(vec![a, b]);
let (indexed, selectivity) = optimizer.analyze_operand(&or_expr);
assert!(!indexed);
assert_abs_diff_eq!(selectivity, 0.5, epsilon = 1e-10); }
#[test]
fn test_estimate_selectivity_known_fields() {
assert_abs_diff_eq!(
Optimizer::estimate_selectivity("kind"),
0.1,
epsilon = 1e-10
);
assert_abs_diff_eq!(
Optimizer::estimate_selectivity("name"),
0.01,
epsilon = 1e-10
);
assert_abs_diff_eq!(
Optimizer::estimate_selectivity("path"),
0.3,
epsilon = 1e-10
);
assert_abs_diff_eq!(
Optimizer::estimate_selectivity("lang"),
0.4,
epsilon = 1e-10
);
assert_abs_diff_eq!(
Optimizer::estimate_selectivity("scope"),
0.35,
epsilon = 1e-10
);
assert_abs_diff_eq!(
Optimizer::estimate_selectivity("text"),
0.5,
epsilon = 1e-10
);
}
#[test]
fn test_estimate_selectivity_unknown_field() {
assert_abs_diff_eq!(
Optimizer::estimate_selectivity("unknown_field"),
0.45,
epsilon = 1e-10
);
}
#[test]
fn test_single_and_operand_unwrapped() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let kind_cond = make_condition("kind", "function");
let expr = Expr::And(vec![kind_cond.clone()]);
let rewritten_expr = optimizer.optimize(expr);
assert_eq!(rewritten_expr, kind_cond);
}
#[test]
fn test_empty_and() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let expr = Expr::And(vec![]);
let rewritten_expr = optimizer.optimize(expr);
match rewritten_expr {
Expr::And(operands) => {
assert_eq!(operands.len(), 0);
}
_ => panic!("Expected And expression"),
}
}
#[test]
fn test_flatten_multiple_and_levels() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let a = make_condition("kind", "function");
let b = make_condition("name", "foo");
let c = make_condition("lang", "rust");
let d = make_condition("path", "src/main.rs");
let expr = Expr::And(vec![
Expr::And(vec![a.clone(), b.clone()]),
Expr::And(vec![c.clone(), d.clone()]),
]);
let rewritten_expr = optimizer.optimize(expr);
match rewritten_expr {
Expr::And(operands) => {
assert_eq!(operands.len(), 4, "Should flatten to 4 operands");
let field_names: Vec<_> = operands
.iter()
.filter_map(|op| match op {
Expr::Condition(cond) => Some(cond.field.as_str()),
_ => None,
})
.collect();
assert_eq!(field_names.len(), 4);
assert!(field_names.contains(&"kind"));
assert!(field_names.contains(&"name"));
assert!(field_names.contains(&"lang"));
assert!(field_names.contains(&"path"));
}
_ => panic!("Expected And expression"),
}
}
#[test]
fn test_flatten_with_mixed_nesting() {
let registry = FieldRegistry::with_core_fields();
let optimizer = Optimizer::new(registry);
let a = make_condition("kind", "function");
let b = make_condition("name", "foo");
let c = make_condition("lang", "rust");
let d = make_condition("path", "src/main.rs");
let expr = Expr::And(vec![
a.clone(),
Expr::And(vec![b.clone(), Expr::And(vec![c.clone(), d.clone()])]),
]);
let rewritten_expr = optimizer.optimize(expr);
match rewritten_expr {
Expr::And(operands) => {
assert_eq!(operands.len(), 4, "Should flatten to 4 operands");
let field_names: Vec<_> = operands
.iter()
.filter_map(|op| match op {
Expr::Condition(cond) => Some(cond.field.as_str()),
_ => None,
})
.collect();
assert_eq!(field_names.len(), 4);
assert!(field_names.contains(&"kind"));
assert!(field_names.contains(&"name"));
assert!(field_names.contains(&"lang"));
assert!(field_names.contains(&"path"));
}
_ => panic!("Expected And expression"),
}
}
}