use chronix_engine::index::{ZoneMapPredicate, ZoneMapResult};
#[derive(Debug, Clone)]
pub struct EncodedDomainEvaluator {
predicates: Vec<ColumnPredicate>,
}
#[derive(Debug, Clone)]
pub struct ColumnPredicate {
pub column: String,
pub predicate: ZoneMapPredicate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RowGroupVerdict {
AllMatch,
Skip,
Decode,
}
impl EncodedDomainEvaluator {
#[must_use]
pub fn new(predicates: Vec<ColumnPredicate>) -> Self {
Self { predicates }
}
#[must_use]
pub fn evaluate<F>(&self, stats_fn: F) -> RowGroupVerdict
where
F: Fn(&str) -> Option<(f64, f64)>,
{
if self.predicates.is_empty() {
return RowGroupVerdict::Decode;
}
let mut all_match = true;
for pred in &self.predicates {
let Some((min, max)) = stats_fn(&pred.column) else {
all_match = false;
continue;
};
match pred.predicate.evaluate(min, max) {
ZoneMapResult::NoneMatch => return RowGroupVerdict::Skip,
ZoneMapResult::AllMatch => { }
ZoneMapResult::MaybeMatch => {
all_match = false;
}
}
}
if all_match {
RowGroupVerdict::AllMatch
} else {
RowGroupVerdict::Decode
}
}
#[must_use]
pub fn evaluate_batch<F>(&self, num_row_groups: usize, stats_fn: F) -> Vec<RowGroupVerdict>
where
F: Fn(usize, &str) -> Option<(f64, f64)>,
{
(0..num_row_groups)
.map(|rg| self.evaluate(|col| stats_fn(rg, col)))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use chronix_engine::index::ZoneMapOp;
fn make_evaluator(col: &str, op: ZoneMapOp, value: f64) -> EncodedDomainEvaluator {
EncodedDomainEvaluator::new(vec![ColumnPredicate {
column: col.to_string(),
predicate: ZoneMapPredicate { op, value },
}])
}
#[test]
fn skip_when_predicate_excludes() {
let eval = make_evaluator("value", ZoneMapOp::Gt, 100.0);
let verdict = eval.evaluate(|_col| Some((10.0, 50.0)));
assert_eq!(verdict, RowGroupVerdict::Skip);
}
#[test]
fn all_match_when_predicate_fully_satisfied() {
let eval = make_evaluator("value", ZoneMapOp::Gt, 5.0);
let verdict = eval.evaluate(|_col| Some((10.0, 100.0)));
assert_eq!(verdict, RowGroupVerdict::AllMatch);
}
#[test]
fn decode_when_predicate_partially_matches() {
let eval = make_evaluator("value", ZoneMapOp::Gt, 50.0);
let verdict = eval.evaluate(|_col| Some((10.0, 100.0)));
assert_eq!(verdict, RowGroupVerdict::Decode);
}
#[test]
fn decode_when_no_stats_available() {
let eval = make_evaluator("value", ZoneMapOp::Gt, 50.0);
let verdict = eval.evaluate(|_col| None);
assert_eq!(verdict, RowGroupVerdict::Decode);
}
#[test]
fn empty_predicates_returns_decode() {
let eval = EncodedDomainEvaluator::new(vec![]);
let verdict = eval.evaluate(|_col| Some((10.0, 100.0)));
assert_eq!(verdict, RowGroupVerdict::Decode);
}
#[test]
fn multiple_predicates_all_match() {
let eval = EncodedDomainEvaluator::new(vec![
ColumnPredicate {
column: "value".to_string(),
predicate: ZoneMapPredicate {
op: ZoneMapOp::Gt,
value: 5.0,
},
},
ColumnPredicate {
column: "value".to_string(),
predicate: ZoneMapPredicate {
op: ZoneMapOp::Lt,
value: 200.0,
},
},
]);
let verdict = eval.evaluate(|_col| Some((10.0, 100.0)));
assert_eq!(verdict, RowGroupVerdict::AllMatch);
}
#[test]
fn multiple_predicates_one_excludes() {
let eval = EncodedDomainEvaluator::new(vec![
ColumnPredicate {
column: "value".to_string(),
predicate: ZoneMapPredicate {
op: ZoneMapOp::Gt,
value: 5.0,
},
},
ColumnPredicate {
column: "value".to_string(),
predicate: ZoneMapPredicate {
op: ZoneMapOp::Lt,
value: 8.0,
},
},
]);
let verdict = eval.evaluate(|_col| Some((10.0, 100.0)));
assert_eq!(verdict, RowGroupVerdict::Skip);
}
#[test]
fn batch_evaluation() {
let eval = make_evaluator("value", ZoneMapOp::Gt, 50.0);
let stats = [
(10.0, 40.0), (60.0, 100.0), (30.0, 80.0), ];
let verdicts = eval.evaluate_batch(3, |rg, _col| Some(stats[rg]));
assert_eq!(verdicts[0], RowGroupVerdict::Skip);
assert_eq!(verdicts[1], RowGroupVerdict::AllMatch);
assert_eq!(verdicts[2], RowGroupVerdict::Decode);
}
#[test]
fn skip_entire_sorted_column() {
let eval = make_evaluator("value", ZoneMapOp::Eq, 75.0);
let rg_stats = [
(0.0, 20.0), (20.0, 40.0), (40.0, 60.0), (60.0, 80.0), (80.0, 100.0), ];
let verdicts = eval.evaluate_batch(5, |rg, _col| Some(rg_stats[rg]));
assert_eq!(
verdicts
.iter()
.filter(|v| **v == RowGroupVerdict::Skip)
.count(),
4
);
assert_eq!(verdicts[3], RowGroupVerdict::Decode);
}
}