1use serde::{Deserialize, Serialize};
13
14use crate::segment::error::{Result, SegmentError};
15use crate::segment::to_array;
16
17#[inline]
26#[must_use]
27pub fn f64_to_ordered_i64(value: f64) -> i64 {
28 let bits = value.to_bits() as i64;
29 if bits < 0 {
30 bits ^ i64::MAX } else {
32 bits
33 }
34}
35
36#[inline]
38#[must_use]
39pub fn ordered_i64_to_f64(ordered: i64) -> f64 {
40 let bits = if ordered < 0 {
41 ordered ^ i64::MAX
42 } else {
43 ordered
44 };
45 f64::from_bits(bits as u64)
46}
47
48#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50pub struct ColumnStats {
51 pub min_value: i64,
57 pub max_value: i64,
59 pub min_value_u64: u64,
61 pub max_value_u64: u64,
63 pub null_count: u64,
65 pub value_count: u64,
67 pub sum: f64,
72 pub sum_i128: i128,
77 pub distinct_count: u32,
79}
80
81impl ColumnStats {
82 #[must_use]
84 pub fn empty() -> Self {
85 Self {
86 min_value: i64::MAX,
87 max_value: i64::MIN,
88 min_value_u64: u64::MAX,
89 max_value_u64: u64::MIN,
90 null_count: 0,
91 value_count: 0,
92 sum: 0.0,
93 sum_i128: 0,
94 distinct_count: 0,
95 }
96 }
97
98 #[must_use]
111 pub fn says_nothing(&self) -> bool {
112 self.value_count == 0 && self.null_count == 0
113 }
114
115 pub fn update_i64(&mut self, value: i64) {
117 self.min_value = self.min_value.min(value);
118 self.max_value = self.max_value.max(value);
119 self.sum += value as f64;
120 self.sum_i128 += i128::from(value);
121 self.value_count += 1;
122 }
123
124 pub fn update_u64(&mut self, value: u64) {
129 self.min_value_u64 = self.min_value_u64.min(value);
130 self.max_value_u64 = self.max_value_u64.max(value);
131 self.sum += value as f64;
132 self.sum_i128 += i128::from(value);
133 self.value_count += 1;
134 }
135
136 pub fn update_decimal(&mut self, mantissa: i128) {
146 let clamped =
147 i64::try_from(mantissa).unwrap_or(if mantissa > 0 { i64::MAX } else { i64::MIN });
148 self.min_value = self.min_value.min(clamped);
149 self.max_value = self.max_value.max(clamped);
150 self.sum_i128 = self.sum_i128.saturating_add(mantissa);
153 self.value_count += 1;
154 }
155
156 pub fn update_f64(&mut self, value: f64) {
165 if !value.is_nan() {
166 let ordered = f64_to_ordered_i64(value);
167 self.min_value = self.min_value.min(ordered);
168 self.max_value = self.max_value.max(ordered);
169 }
170 if value.is_finite() {
171 self.sum += value;
172 }
173 self.value_count += 1;
174 }
175
176 pub fn update_bool(&mut self, value: bool) {
178 let v = i64::from(value);
179 self.min_value = self.min_value.min(v);
180 self.max_value = self.max_value.max(v);
181 self.value_count += 1;
182 }
183
184 pub fn record_null(&mut self) {
186 self.null_count += 1;
187 }
188
189 #[must_use]
191 pub fn to_bytes(&self) -> Vec<u8> {
192 let mut buf = Vec::with_capacity(76);
193 buf.extend_from_slice(&self.min_value.to_le_bytes());
194 buf.extend_from_slice(&self.max_value.to_le_bytes());
195 buf.extend_from_slice(&self.min_value_u64.to_le_bytes());
196 buf.extend_from_slice(&self.max_value_u64.to_le_bytes());
197 buf.extend_from_slice(&self.null_count.to_le_bytes());
198 buf.extend_from_slice(&self.value_count.to_le_bytes());
199 buf.extend_from_slice(&self.sum.to_le_bytes());
200 buf.extend_from_slice(&self.sum_i128.to_le_bytes());
201 buf.extend_from_slice(&self.distinct_count.to_le_bytes());
202 buf
203 }
204
205 pub fn from_bytes(data: &[u8]) -> Result<Self> {
211 if data.len() < 76 {
212 return Err(SegmentError::CorruptFile {
213 detail: format!("ColumnStats requires 76 bytes, got {}", data.len()),
214 });
215 }
216 Ok(Self {
217 min_value: i64::from_le_bytes(to_array!(data[0..8], "stats min_value")?),
218 max_value: i64::from_le_bytes(to_array!(data[8..16], "stats max_value")?),
219 min_value_u64: u64::from_le_bytes(to_array!(data[16..24], "stats min_value_u64")?),
220 max_value_u64: u64::from_le_bytes(to_array!(data[24..32], "stats max_value_u64")?),
221 null_count: u64::from_le_bytes(to_array!(data[32..40], "stats null_count")?),
222 value_count: u64::from_le_bytes(to_array!(data[40..48], "stats value_count")?),
223 sum: f64::from_le_bytes(to_array!(data[48..56], "stats sum")?),
224 sum_i128: i128::from_le_bytes(to_array!(data[56..72], "stats sum_i128")?),
225 distinct_count: u32::from_le_bytes(to_array!(data[72..76], "stats distinct_count")?),
226 })
227 }
228}
229
230pub const COLUMN_STATS_SIZE: usize = 76;
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn stats_roundtrip() {
239 let mut stats = ColumnStats::empty();
240 stats.update_i64(10);
241 stats.update_i64(20);
242 stats.update_i64(30);
243
244 let bytes = stats.to_bytes();
245 assert_eq!(bytes.len(), COLUMN_STATS_SIZE);
246 let recovered = ColumnStats::from_bytes(&bytes).unwrap();
247 assert_eq!(stats, recovered);
248 }
249
250 #[test]
251 fn stats_i64_tracking() {
252 let mut stats = ColumnStats::empty();
253 stats.update_i64(-5);
254 stats.update_i64(10);
255 stats.update_i64(3);
256
257 assert_eq!(stats.min_value, -5);
258 assert_eq!(stats.max_value, 10);
259 assert_eq!(stats.value_count, 3);
260 assert!((stats.sum - 8.0).abs() < f64::EPSILON);
261 assert_eq!(stats.sum_i128, 8);
262 }
263
264 #[test]
265 fn stats_f64_tracking() {
266 let mut stats = ColumnStats::empty();
267 stats.update_f64(1.5);
268 stats.update_f64(2.5);
269 stats.update_f64(f64::NAN);
270
271 assert_eq!(stats.value_count, 3);
272 assert!((stats.sum - 4.0).abs() < f64::EPSILON);
274 }
275
276 #[test]
277 fn stats_f64_negative_ordering() {
278 let mut stats = ColumnStats::empty();
279 stats.update_f64(-10.0);
280 stats.update_f64(-1.0);
281 stats.update_f64(5.0);
282
283 let min_f = ordered_i64_to_f64(stats.min_value);
285 let max_f = ordered_i64_to_f64(stats.max_value);
286 assert!(
287 (min_f - (-10.0)).abs() < f64::EPSILON,
288 "min should be -10.0, got {min_f}"
289 );
290 assert!(
291 (max_f - 5.0).abs() < f64::EPSILON,
292 "max should be 5.0, got {max_f}"
293 );
294 }
295
296 #[test]
297 fn f64_total_ordering() {
298 let values = [
300 f64::NEG_INFINITY,
301 -1e100,
302 -1.0,
303 -f64::MIN_POSITIVE,
304 -0.0,
305 0.0,
306 f64::MIN_POSITIVE,
307 1.0,
308 1e100,
309 f64::INFINITY,
310 ];
311 for window in values.windows(2) {
312 let a = f64_to_ordered_i64(window[0]);
313 let b = f64_to_ordered_i64(window[1]);
314 assert!(
315 a <= b,
316 "ordering violated: {:?} (={a}) should <= {:?} (={b})",
317 window[0],
318 window[1]
319 );
320 }
321 }
322
323 #[test]
324 fn stats_u64_large_values() {
325 let mut stats = ColumnStats::empty();
326 stats.update_u64(u64::MAX);
327 stats.update_u64(0);
328
329 assert_eq!(stats.max_value_u64, u64::MAX);
331 assert_eq!(stats.min_value_u64, 0);
332 assert_eq!(stats.sum_i128, u64::MAX as i128);
334 }
335
336 #[test]
337 fn stats_u64_above_i64_max() {
338 let mut stats = ColumnStats::empty();
339 let big = (i64::MAX as u64) + 100;
340 stats.update_u64(big);
341 stats.update_u64(42);
342
343 assert_eq!(stats.max_value_u64, big);
344 assert_eq!(stats.min_value_u64, 42);
345 assert_eq!(stats.value_count, 2);
346 assert_eq!(stats.sum_i128, big as i128 + 42);
347 }
348
349 #[test]
350 fn stats_from_bytes_too_short() {
351 assert!(ColumnStats::from_bytes(&[0u8; 10]).is_err());
352 }
353
354 #[test]
355 fn stats_null_tracking() {
356 let mut stats = ColumnStats::empty();
357 stats.update_i64(42);
358 stats.record_null();
359 stats.record_null();
360
361 assert_eq!(stats.value_count, 1);
362 assert_eq!(stats.null_count, 2);
363 }
364
365 #[test]
366 fn stats_empty() {
367 let stats = ColumnStats::empty();
368 assert_eq!(stats.min_value, i64::MAX);
369 assert_eq!(stats.max_value, i64::MIN);
370 assert_eq!(stats.value_count, 0);
371 assert_eq!(stats.null_count, 0);
372 }
373
374 #[test]
375 fn stats_f64_nan_excluded_from_min_max() {
376 let mut stats = ColumnStats::empty();
377 stats.update_f64(f64::NAN);
378 assert_eq!(stats.min_value, i64::MAX, "NaN corrupted min_value");
380 assert_eq!(stats.max_value, i64::MIN, "NaN corrupted max_value");
381 assert_eq!(stats.value_count, 1);
382
383 stats.update_f64(42.0);
385 stats.update_f64(f64::NAN);
386 let max_f = ordered_i64_to_f64(stats.max_value);
387 assert!(
388 (max_f - 42.0).abs() < f64::EPSILON,
389 "max should be 42.0, got {max_f}"
390 );
391 }
392
393 #[test]
394 fn stats_bool_tracking() {
395 let mut stats = ColumnStats::empty();
396 stats.update_bool(false);
397 stats.update_bool(true);
398 stats.update_bool(false);
399
400 assert_eq!(stats.min_value, 0);
401 assert_eq!(stats.max_value, 1);
402 assert_eq!(stats.value_count, 3);
403 }
404
405 #[test]
406 fn stats_f64_infinity() {
407 let mut stats = ColumnStats::empty();
408 stats.update_f64(f64::NEG_INFINITY);
409 stats.update_f64(f64::INFINITY);
410
411 let min_f = ordered_i64_to_f64(stats.min_value);
412 let max_f = ordered_i64_to_f64(stats.max_value);
413 assert!(min_f.is_infinite() && min_f.is_sign_negative());
414 assert!(max_f.is_infinite() && max_f.is_sign_positive());
415 assert!(stats.sum.abs() < f64::EPSILON);
417 }
418
419 #[test]
420 fn stats_i128_precision_beyond_f64() {
421 let mut stats = ColumnStats::empty();
424 let big: i64 = (1_i64 << 53) + 1; stats.update_i64(big);
426 stats.update_i64(big);
427
428 let expected_i128 = 2 * (big as i128);
430 assert_eq!(stats.sum_i128, expected_i128);
431
432 let bytes = stats.to_bytes();
434 let recovered = ColumnStats::from_bytes(&bytes).unwrap();
435 assert_eq!(recovered.sum_i128, expected_i128);
436 }
437}