use crate::query::planner::{FilterStep, QueryPlan, ScanType};
pub fn optimize(plan: &mut QueryPlan) -> Vec<String> {
let mut applied = Vec::new();
if pushdown_predicates(plan) {
applied.push("predicate_pushdown".into());
}
if propagate_limit(plan) {
applied.push("limit_propagation".into());
}
if reorder_filters(plan) {
applied.push("filter_reorder".into());
}
applied
}
fn pushdown_predicates(plan: &mut QueryPlan) -> bool {
if !matches!(plan.scan, ScanType::FullScan) {
return false;
}
let mut pushed = false;
plan.filters.retain(|f| {
if pushed {
return true;
}
match f {
FilterStep::KeyPattern(kp) => {
plan.scan = match kp {
crate::query::ast::KeyPattern::Exact(k) => ScanType::IndexExact(k.clone()),
crate::query::ast::KeyPattern::Prefix(p) => ScanType::IndexPrefix(p.clone()),
other => ScanType::PatternScan(other.clone()),
};
pushed = true;
false }
_ => true,
}
});
pushed
}
fn propagate_limit(plan: &mut QueryPlan) -> bool {
if plan.aggregation.is_some() {
return false; }
if plan.pagination.is_none() {
return false;
}
true
}
fn reorder_filters(plan: &mut QueryPlan) -> bool {
if plan.filters.len() < 2 {
return false;
}
let original_order: Vec<usize> = (0..plan.filters.len()).collect();
plan.filters.sort_by_key(|f| match f {
FilterStep::TimeRange(_) => 0,
FilterStep::ValueComparison { .. } => 1,
FilterStep::KeyPattern(_) => 2,
FilterStep::Boolean { .. } => 3,
});
let new_order: Vec<usize> = (0..plan.filters.len()).collect();
original_order != new_order
}
#[cfg(test)]
mod tests {
use super::*;
use crate::query::parser::parse_eql;
use crate::query::planner::QueryPlan;
#[test]
fn optimizer_propagates_limit() {
let ast = parse_eql("SELECT * FROM \"k\" LIMIT 10").unwrap();
let mut plan = QueryPlan::from_ast(&ast, 1000).unwrap();
let applied = optimize(&mut plan);
assert!(applied.contains(&"limit_propagation".into()));
}
#[test]
fn optimizer_does_not_propagate_limit_with_aggregation() {
let ast = parse_eql("SELECT AVG(value) FROM \"k\"").unwrap();
let mut plan = QueryPlan::from_ast(&ast, 1000).unwrap();
let applied = optimize(&mut plan);
assert!(!applied.contains(&"limit_propagation".into()));
}
#[test]
fn optimizer_reorders_filters() {
let ast = parse_eql("SELECT * FROM \"k\" WHERE value > 1 AND timestamp BETWEEN 0 AND 100")
.unwrap();
let mut plan = QueryPlan::from_ast(&ast, 1000).unwrap();
let applied = optimize(&mut plan);
assert!(plan.filters.len() >= 1);
}
}