1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ZoneMapOp {
10 Eq,
12 Ne,
14 Gt,
16 Ge,
18 Lt,
20 Le,
22}
23
24#[derive(Debug, Clone, Copy)]
26pub struct ZoneMapPredicate {
27 pub op: ZoneMapOp,
29 pub value: f64,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum ZoneMapResult {
36 AllMatch,
38 NoneMatch,
40 MaybeMatch,
42}
43
44impl ZoneMapPredicate {
45 #[must_use]
55 pub fn evaluate(&self, min: f64, max: f64) -> ZoneMapResult {
56 if self.value.is_nan() || min.is_nan() || max.is_nan() {
59 return ZoneMapResult::MaybeMatch;
60 }
61
62 match self.op {
63 ZoneMapOp::Eq => {
64 if self.value < min || self.value > max {
65 ZoneMapResult::NoneMatch
66 } else if min.to_bits() == max.to_bits() && min.to_bits() == self.value.to_bits() {
67 ZoneMapResult::AllMatch
68 } else {
69 ZoneMapResult::MaybeMatch
70 }
71 }
72 ZoneMapOp::Ne => {
73 if min.to_bits() == max.to_bits() && min.to_bits() == self.value.to_bits() {
74 ZoneMapResult::NoneMatch
75 } else if self.value < min || self.value > max {
76 ZoneMapResult::AllMatch
77 } else {
78 ZoneMapResult::MaybeMatch
79 }
80 }
81 ZoneMapOp::Gt => {
82 if min > self.value {
83 ZoneMapResult::AllMatch
84 } else if max <= self.value {
85 ZoneMapResult::NoneMatch
86 } else {
87 ZoneMapResult::MaybeMatch
88 }
89 }
90 ZoneMapOp::Ge => {
91 if min >= self.value {
92 ZoneMapResult::AllMatch
93 } else if max < self.value {
94 ZoneMapResult::NoneMatch
95 } else {
96 ZoneMapResult::MaybeMatch
97 }
98 }
99 ZoneMapOp::Lt => {
100 if max < self.value {
101 ZoneMapResult::AllMatch
102 } else if min >= self.value {
103 ZoneMapResult::NoneMatch
104 } else {
105 ZoneMapResult::MaybeMatch
106 }
107 }
108 ZoneMapOp::Le => {
109 if max <= self.value {
110 ZoneMapResult::AllMatch
111 } else if min > self.value {
112 ZoneMapResult::NoneMatch
113 } else {
114 ZoneMapResult::MaybeMatch
115 }
116 }
117 }
118 }
119}
120
121#[must_use]
125pub fn segment_overlaps_range(
126 seg_min_ts: i64,
127 seg_max_ts: i64,
128 range_start: i64,
129 range_end: i64,
130) -> bool {
131 seg_max_ts >= range_start && seg_min_ts <= range_end
132}
133
134#[must_use]
140pub fn prune_row_groups(
141 row_group_stats: &[(f64, f64)], predicate: &ZoneMapPredicate,
143) -> Vec<bool> {
144 row_group_stats
145 .iter()
146 .map(|&(min, max)| predicate.evaluate(min, max) != ZoneMapResult::NoneMatch)
147 .collect()
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn eq_inside_range() {
156 let pred = ZoneMapPredicate {
157 op: ZoneMapOp::Eq,
158 value: 50.0,
159 };
160 assert_eq!(pred.evaluate(0.0, 100.0), ZoneMapResult::MaybeMatch);
161 }
162
163 #[test]
164 fn eq_outside_range() {
165 let pred = ZoneMapPredicate {
166 op: ZoneMapOp::Eq,
167 value: 200.0,
168 };
169 assert_eq!(pred.evaluate(0.0, 100.0), ZoneMapResult::NoneMatch);
170 }
171
172 #[test]
173 fn eq_constant_column() {
174 let pred = ZoneMapPredicate {
175 op: ZoneMapOp::Eq,
176 value: 42.0,
177 };
178 assert_eq!(pred.evaluate(42.0, 42.0), ZoneMapResult::AllMatch);
179 }
180
181 #[test]
182 fn gt_entire_range_above() {
183 let pred = ZoneMapPredicate {
184 op: ZoneMapOp::Gt,
185 value: 10.0,
186 };
187 assert_eq!(pred.evaluate(50.0, 100.0), ZoneMapResult::AllMatch);
188 }
189
190 #[test]
191 fn gt_entire_range_below() {
192 let pred = ZoneMapPredicate {
193 op: ZoneMapOp::Gt,
194 value: 100.0,
195 };
196 assert_eq!(pred.evaluate(50.0, 100.0), ZoneMapResult::NoneMatch);
197 }
198
199 #[test]
200 fn lt_entire_range_below() {
201 let pred = ZoneMapPredicate {
202 op: ZoneMapOp::Lt,
203 value: 200.0,
204 };
205 assert_eq!(pred.evaluate(50.0, 100.0), ZoneMapResult::AllMatch);
206 }
207
208 #[test]
209 fn lt_entire_range_above() {
210 let pred = ZoneMapPredicate {
211 op: ZoneMapOp::Lt,
212 value: 10.0,
213 };
214 assert_eq!(pred.evaluate(50.0, 100.0), ZoneMapResult::NoneMatch);
215 }
216
217 #[test]
218 fn ne_constant_column_matches_value() {
219 let pred = ZoneMapPredicate {
220 op: ZoneMapOp::Ne,
221 value: 42.0,
222 };
223 assert_eq!(pred.evaluate(42.0, 42.0), ZoneMapResult::NoneMatch);
224 }
225
226 #[test]
227 fn ne_value_outside_range() {
228 let pred = ZoneMapPredicate {
229 op: ZoneMapOp::Ne,
230 value: 200.0,
231 };
232 assert_eq!(pred.evaluate(0.0, 100.0), ZoneMapResult::AllMatch);
233 }
234
235 #[test]
236 fn ge_at_boundary() {
237 let pred = ZoneMapPredicate {
238 op: ZoneMapOp::Ge,
239 value: 50.0,
240 };
241 assert_eq!(pred.evaluate(50.0, 100.0), ZoneMapResult::AllMatch);
242 }
243
244 #[test]
245 fn le_at_boundary() {
246 let pred = ZoneMapPredicate {
247 op: ZoneMapOp::Le,
248 value: 100.0,
249 };
250 assert_eq!(pred.evaluate(50.0, 100.0), ZoneMapResult::AllMatch);
251 }
252
253 #[test]
254 fn prune_row_groups_mixed() {
255 let stats = vec![
257 (0.0, 10.0), (20.0, 30.0), (50.0, 60.0), (5.0, 15.0), (100.0, 200.0), ];
263 let pred = ZoneMapPredicate {
264 op: ZoneMapOp::Lt,
265 value: 40.0,
266 };
267 let result = prune_row_groups(&stats, &pred);
268 assert_eq!(result, vec![true, true, false, true, false]);
269 }
270
271 #[test]
272 fn segment_overlap_basic() {
273 assert!(segment_overlaps_range(100, 200, 150, 250));
274 assert!(segment_overlaps_range(100, 200, 50, 150));
275 assert!(!segment_overlaps_range(100, 200, 300, 400));
276 assert!(!segment_overlaps_range(100, 200, 0, 50));
277 assert!(segment_overlaps_range(100, 200, 100, 200)); }
279
280 #[test]
283 fn nan_value_returns_maybe_match() {
284 let pred = ZoneMapPredicate {
285 op: ZoneMapOp::Eq,
286 value: f64::NAN,
287 };
288 assert_eq!(pred.evaluate(0.0, 100.0), ZoneMapResult::MaybeMatch);
289 }
290
291 #[test]
292 fn nan_min_returns_maybe_match() {
293 let pred = ZoneMapPredicate {
294 op: ZoneMapOp::Gt,
295 value: 50.0,
296 };
297 assert_eq!(pred.evaluate(f64::NAN, 100.0), ZoneMapResult::MaybeMatch);
298 }
299
300 #[test]
301 fn nan_max_returns_maybe_match() {
302 let pred = ZoneMapPredicate {
303 op: ZoneMapOp::Lt,
304 value: 50.0,
305 };
306 assert_eq!(pred.evaluate(0.0, f64::NAN), ZoneMapResult::MaybeMatch);
307 }
308
309 #[test]
310 fn nan_all_returns_maybe_match() {
311 for op in [
312 ZoneMapOp::Eq,
313 ZoneMapOp::Ne,
314 ZoneMapOp::Gt,
315 ZoneMapOp::Ge,
316 ZoneMapOp::Lt,
317 ZoneMapOp::Le,
318 ] {
319 let pred = ZoneMapPredicate {
320 op,
321 value: f64::NAN,
322 };
323 assert_eq!(
324 pred.evaluate(f64::NAN, f64::NAN),
325 ZoneMapResult::MaybeMatch,
326 "op={op:?} with all-NaN should be MaybeMatch"
327 );
328 }
329 }
330
331 #[test]
332 fn nan_in_prune_row_groups_keeps_nan_groups() {
333 let stats = vec![
334 (0.0, 100.0), (f64::NAN, 50.0), (10.0, f64::NAN), ];
338 let pred = ZoneMapPredicate {
339 op: ZoneMapOp::Gt,
340 value: 200.0,
341 };
342 let result = prune_row_groups(&stats, &pred);
343 assert_eq!(result, vec![false, true, true]);
345 }
346}