use std::collections::HashMap;
use super::filter_compiler::{CompiledFilter, FilterCompiler, FilterCompilerError, FilterExpr};
pub type AggVarMap = HashMap<String, usize>;
pub struct HavingCompiler {
inner: FilterCompiler,
}
impl HavingCompiler {
pub fn new() -> Self {
HavingCompiler {
inner: FilterCompiler,
}
}
pub fn compile_having(
&mut self,
expr: FilterExpr,
agg_var_map: AggVarMap,
) -> Result<CompiledFilter, FilterCompilerError> {
validate_vars(&expr, &agg_var_map)?;
self.inner.compile(&expr, agg_var_map).and_then(|opt| {
opt.ok_or_else(|| {
FilterCompilerError::UnsupportedExpression(
"filter compiler returned None for a validated HAVING FilterExpr".to_string(),
)
})
})
}
}
impl Default for HavingCompiler {
fn default() -> Self {
Self::new()
}
}
fn validate_vars(expr: &FilterExpr, map: &AggVarMap) -> Result<(), FilterCompilerError> {
match expr {
FilterExpr::Variable(name) => {
if !map.contains_key(name.as_str()) {
return Err(FilterCompilerError::UnsupportedExpression(format!(
"variable '{}' not in agg_var_map",
name
)));
}
}
FilterExpr::BinOp { left, right, .. } => {
validate_vars(left, map)?;
validate_vars(right, map)?;
}
FilterExpr::UnaryNot(inner) => {
validate_vars(inner, map)?;
}
FilterExpr::Builtin { arg: inner, .. } => {
validate_vars(inner, map)?;
}
FilterExpr::Literal(_) => {
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::jit::filter_compiler::BinOp;
fn compiler() -> HavingCompiler {
HavingCompiler::new()
}
fn agg_map_single(name: &str) -> AggVarMap {
let mut m = AggVarMap::new();
m.insert(name.to_string(), 0);
m
}
#[test]
fn test_sum_gt_threshold_pass() {
let mut c = compiler();
let map = agg_map_single("sum");
let expr = FilterExpr::BinOp {
op: BinOp::Gt,
left: Box::new(FilterExpr::Variable("sum".to_string())),
right: Box::new(FilterExpr::Literal(100.0)),
};
let cf = c.compile_having(expr, map).expect("compile ok");
let mut binding = HashMap::new();
binding.insert("sum".to_string(), 150.0f64);
assert_eq!(cf.evaluate(&binding), Some(true));
}
#[test]
fn test_unknown_var_returns_error() {
let mut c = compiler();
let map = agg_map_single("sum");
let expr = FilterExpr::BinOp {
op: BinOp::Gt,
left: Box::new(FilterExpr::Variable("unknown".to_string())),
right: Box::new(FilterExpr::Literal(0.0)),
};
let result = c.compile_having(expr, map);
assert!(
matches!(result, Err(FilterCompilerError::UnsupportedExpression(_))),
"expected UnsupportedExpression, got: {:?}",
result
);
}
#[test]
fn test_literal_only_having() {
let mut c = compiler();
let expr = FilterExpr::BinOp {
op: BinOp::Lt,
left: Box::new(FilterExpr::Literal(1.0)),
right: Box::new(FilterExpr::Literal(2.0)),
};
let cf = c
.compile_having(expr, AggVarMap::new())
.expect("compile ok");
let empty: HashMap<String, f64> = HashMap::new();
assert_eq!(cf.evaluate(&empty), Some(true));
}
}