#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ZoneMapOp {
Eq,
Ne,
Gt,
Ge,
Lt,
Le,
}
#[derive(Debug, Clone, Copy)]
pub struct ZoneMapPredicate {
pub op: ZoneMapOp,
pub value: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ZoneMapResult {
AllMatch,
NoneMatch,
MaybeMatch,
}
impl ZoneMapPredicate {
#[must_use]
pub fn evaluate(&self, min: f64, max: f64) -> ZoneMapResult {
if self.value.is_nan() || min.is_nan() || max.is_nan() {
return ZoneMapResult::MaybeMatch;
}
match self.op {
ZoneMapOp::Eq => {
if self.value < min || self.value > max {
ZoneMapResult::NoneMatch
} else if min.to_bits() == max.to_bits() && min.to_bits() == self.value.to_bits() {
ZoneMapResult::AllMatch
} else {
ZoneMapResult::MaybeMatch
}
}
ZoneMapOp::Ne => {
if min.to_bits() == max.to_bits() && min.to_bits() == self.value.to_bits() {
ZoneMapResult::NoneMatch
} else if self.value < min || self.value > max {
ZoneMapResult::AllMatch
} else {
ZoneMapResult::MaybeMatch
}
}
ZoneMapOp::Gt => {
if min > self.value {
ZoneMapResult::AllMatch
} else if max <= self.value {
ZoneMapResult::NoneMatch
} else {
ZoneMapResult::MaybeMatch
}
}
ZoneMapOp::Ge => {
if min >= self.value {
ZoneMapResult::AllMatch
} else if max < self.value {
ZoneMapResult::NoneMatch
} else {
ZoneMapResult::MaybeMatch
}
}
ZoneMapOp::Lt => {
if max < self.value {
ZoneMapResult::AllMatch
} else if min >= self.value {
ZoneMapResult::NoneMatch
} else {
ZoneMapResult::MaybeMatch
}
}
ZoneMapOp::Le => {
if max <= self.value {
ZoneMapResult::AllMatch
} else if min > self.value {
ZoneMapResult::NoneMatch
} else {
ZoneMapResult::MaybeMatch
}
}
}
}
}
#[must_use]
pub fn segment_overlaps_range(
seg_min_ts: i64,
seg_max_ts: i64,
range_start: i64,
range_end: i64,
) -> bool {
seg_max_ts >= range_start && seg_min_ts <= range_end
}
#[must_use]
pub fn prune_row_groups(
row_group_stats: &[(f64, f64)], predicate: &ZoneMapPredicate,
) -> Vec<bool> {
row_group_stats
.iter()
.map(|&(min, max)| predicate.evaluate(min, max) != ZoneMapResult::NoneMatch)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn eq_inside_range() {
let pred = ZoneMapPredicate {
op: ZoneMapOp::Eq,
value: 50.0,
};
assert_eq!(pred.evaluate(0.0, 100.0), ZoneMapResult::MaybeMatch);
}
#[test]
fn eq_outside_range() {
let pred = ZoneMapPredicate {
op: ZoneMapOp::Eq,
value: 200.0,
};
assert_eq!(pred.evaluate(0.0, 100.0), ZoneMapResult::NoneMatch);
}
#[test]
fn eq_constant_column() {
let pred = ZoneMapPredicate {
op: ZoneMapOp::Eq,
value: 42.0,
};
assert_eq!(pred.evaluate(42.0, 42.0), ZoneMapResult::AllMatch);
}
#[test]
fn gt_entire_range_above() {
let pred = ZoneMapPredicate {
op: ZoneMapOp::Gt,
value: 10.0,
};
assert_eq!(pred.evaluate(50.0, 100.0), ZoneMapResult::AllMatch);
}
#[test]
fn gt_entire_range_below() {
let pred = ZoneMapPredicate {
op: ZoneMapOp::Gt,
value: 100.0,
};
assert_eq!(pred.evaluate(50.0, 100.0), ZoneMapResult::NoneMatch);
}
#[test]
fn lt_entire_range_below() {
let pred = ZoneMapPredicate {
op: ZoneMapOp::Lt,
value: 200.0,
};
assert_eq!(pred.evaluate(50.0, 100.0), ZoneMapResult::AllMatch);
}
#[test]
fn lt_entire_range_above() {
let pred = ZoneMapPredicate {
op: ZoneMapOp::Lt,
value: 10.0,
};
assert_eq!(pred.evaluate(50.0, 100.0), ZoneMapResult::NoneMatch);
}
#[test]
fn ne_constant_column_matches_value() {
let pred = ZoneMapPredicate {
op: ZoneMapOp::Ne,
value: 42.0,
};
assert_eq!(pred.evaluate(42.0, 42.0), ZoneMapResult::NoneMatch);
}
#[test]
fn ne_value_outside_range() {
let pred = ZoneMapPredicate {
op: ZoneMapOp::Ne,
value: 200.0,
};
assert_eq!(pred.evaluate(0.0, 100.0), ZoneMapResult::AllMatch);
}
#[test]
fn ge_at_boundary() {
let pred = ZoneMapPredicate {
op: ZoneMapOp::Ge,
value: 50.0,
};
assert_eq!(pred.evaluate(50.0, 100.0), ZoneMapResult::AllMatch);
}
#[test]
fn le_at_boundary() {
let pred = ZoneMapPredicate {
op: ZoneMapOp::Le,
value: 100.0,
};
assert_eq!(pred.evaluate(50.0, 100.0), ZoneMapResult::AllMatch);
}
#[test]
fn prune_row_groups_mixed() {
let stats = vec![
(0.0, 10.0), (20.0, 30.0), (50.0, 60.0), (5.0, 15.0), (100.0, 200.0), ];
let pred = ZoneMapPredicate {
op: ZoneMapOp::Lt,
value: 40.0,
};
let result = prune_row_groups(&stats, &pred);
assert_eq!(result, vec![true, true, false, true, false]);
}
#[test]
fn segment_overlap_basic() {
assert!(segment_overlaps_range(100, 200, 150, 250));
assert!(segment_overlaps_range(100, 200, 50, 150));
assert!(!segment_overlaps_range(100, 200, 300, 400));
assert!(!segment_overlaps_range(100, 200, 0, 50));
assert!(segment_overlaps_range(100, 200, 100, 200)); }
#[test]
fn nan_value_returns_maybe_match() {
let pred = ZoneMapPredicate {
op: ZoneMapOp::Eq,
value: f64::NAN,
};
assert_eq!(pred.evaluate(0.0, 100.0), ZoneMapResult::MaybeMatch);
}
#[test]
fn nan_min_returns_maybe_match() {
let pred = ZoneMapPredicate {
op: ZoneMapOp::Gt,
value: 50.0,
};
assert_eq!(pred.evaluate(f64::NAN, 100.0), ZoneMapResult::MaybeMatch);
}
#[test]
fn nan_max_returns_maybe_match() {
let pred = ZoneMapPredicate {
op: ZoneMapOp::Lt,
value: 50.0,
};
assert_eq!(pred.evaluate(0.0, f64::NAN), ZoneMapResult::MaybeMatch);
}
#[test]
fn nan_all_returns_maybe_match() {
for op in [
ZoneMapOp::Eq,
ZoneMapOp::Ne,
ZoneMapOp::Gt,
ZoneMapOp::Ge,
ZoneMapOp::Lt,
ZoneMapOp::Le,
] {
let pred = ZoneMapPredicate {
op,
value: f64::NAN,
};
assert_eq!(
pred.evaluate(f64::NAN, f64::NAN),
ZoneMapResult::MaybeMatch,
"op={op:?} with all-NaN should be MaybeMatch"
);
}
}
#[test]
fn nan_in_prune_row_groups_keeps_nan_groups() {
let stats = vec![
(0.0, 100.0), (f64::NAN, 50.0), (10.0, f64::NAN), ];
let pred = ZoneMapPredicate {
op: ZoneMapOp::Gt,
value: 200.0,
};
let result = prune_row_groups(&stats, &pred);
assert_eq!(result, vec![false, true, true]);
}
}