use crate::ast::{CompareOp, FieldRef, Filter};
use reddb_types::types::Value;
pub fn optimize(filter: Filter) -> Filter {
match filter {
Filter::And(l, r) => {
let l = optimize(*l);
let r = optimize(*r);
Filter::And(Box::new(l), Box::new(r))
}
Filter::Or(l, r) => {
let l = optimize(*l);
let r = optimize(*r);
let or_node = Filter::Or(Box::new(l), Box::new(r));
try_or_to_in(or_node)
}
Filter::Not(inner) => Filter::Not(Box::new(optimize(*inner))),
other => other,
}
}
fn try_or_to_in(or: Filter) -> Filter {
let mut values: Vec<Value> = Vec::new();
let mut field: Option<FieldRef> = None;
if collect_eq_leaves(&or, &mut field, &mut values) {
if let Some(f) = field {
values.dedup();
return Filter::In { field: f, values };
}
}
or
}
fn collect_eq_leaves(
filter: &Filter,
field: &mut Option<FieldRef>,
values: &mut Vec<Value>,
) -> bool {
match filter {
Filter::Or(l, r) => {
collect_eq_leaves(l, field, values) && collect_eq_leaves(r, field, values)
}
Filter::Compare {
field: f,
op: CompareOp::Eq,
value: v,
} => {
match field {
None => {
*field = Some(f.clone());
}
Some(existing) => {
if existing != f {
return false; }
}
}
values.push(v.clone());
true
}
Filter::In {
field: f,
values: vs,
} => {
match field {
None => {
*field = Some(f.clone());
}
Some(existing) => {
if existing != f {
return false;
}
}
}
values.extend(vs.iter().cloned());
true
}
_ => false, }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{FieldRef, Filter};
use reddb_types::types::Value;
fn field(name: &str) -> FieldRef {
FieldRef::TableColumn {
table: String::new(),
column: name.to_string(),
}
}
fn eq(col: &str, val: Value) -> Filter {
Filter::Compare {
field: field(col),
op: CompareOp::Eq,
value: val,
}
}
fn or(a: Filter, b: Filter) -> Filter {
Filter::Or(Box::new(a), Box::new(b))
}
#[test]
fn test_or_two_eq_same_field_becomes_in() {
let f = or(
eq("city", Value::text("NYC")),
eq("city", Value::text("LA")),
);
let opt = optimize(f);
match opt {
Filter::In {
field: FieldRef::TableColumn { column, .. },
values,
} => {
assert_eq!(column, "city");
assert_eq!(values.len(), 2);
}
other => panic!("expected In, got {:?}", other),
}
}
#[test]
fn test_or_different_fields_stays_or() {
let f = or(
eq("city", Value::text("NYC")),
eq("age", Value::Integer(30)),
);
let opt = optimize(f);
assert!(matches!(opt, Filter::Or(_, _)));
}
#[test]
fn test_three_level_or_becomes_in() {
let f = or(
or(
eq("status", Value::text("a")),
eq("status", Value::text("b")),
),
eq("status", Value::text("c")),
);
let opt = optimize(f);
match opt {
Filter::In { values, .. } => assert_eq!(values.len(), 3),
other => panic!("expected In, got {:?}", other),
}
}
#[test]
fn test_and_left_unchanged() {
let f = Filter::And(
Box::new(eq("a", Value::Integer(1))),
Box::new(eq("b", Value::Integer(2))),
);
let opt = optimize(f);
assert!(matches!(opt, Filter::And(_, _)));
}
}