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_f64(&mut self, value: f64) {
145 if !value.is_nan() {
146 let ordered = f64_to_ordered_i64(value);
147 self.min_value = self.min_value.min(ordered);
148 self.max_value = self.max_value.max(ordered);
149 }
150 if value.is_finite() {
151 self.sum += value;
152 }
153 self.value_count += 1;
154 }
155
156 pub fn update_bool(&mut self, value: bool) {
158 let v = i64::from(value);
159 self.min_value = self.min_value.min(v);
160 self.max_value = self.max_value.max(v);
161 self.value_count += 1;
162 }
163
164 pub fn record_null(&mut self) {
166 self.null_count += 1;
167 }
168
169 #[must_use]
171 pub fn to_bytes(&self) -> Vec<u8> {
172 let mut buf = Vec::with_capacity(76);
173 buf.extend_from_slice(&self.min_value.to_le_bytes());
174 buf.extend_from_slice(&self.max_value.to_le_bytes());
175 buf.extend_from_slice(&self.min_value_u64.to_le_bytes());
176 buf.extend_from_slice(&self.max_value_u64.to_le_bytes());
177 buf.extend_from_slice(&self.null_count.to_le_bytes());
178 buf.extend_from_slice(&self.value_count.to_le_bytes());
179 buf.extend_from_slice(&self.sum.to_le_bytes());
180 buf.extend_from_slice(&self.sum_i128.to_le_bytes());
181 buf.extend_from_slice(&self.distinct_count.to_le_bytes());
182 buf
183 }
184
185 pub fn from_bytes(data: &[u8]) -> Result<Self> {
191 if data.len() < 76 {
192 return Err(SegmentError::CorruptFile {
193 detail: format!("ColumnStats requires 76 bytes, got {}", data.len()),
194 });
195 }
196 Ok(Self {
197 min_value: i64::from_le_bytes(to_array!(data[0..8], "stats min_value")?),
198 max_value: i64::from_le_bytes(to_array!(data[8..16], "stats max_value")?),
199 min_value_u64: u64::from_le_bytes(to_array!(data[16..24], "stats min_value_u64")?),
200 max_value_u64: u64::from_le_bytes(to_array!(data[24..32], "stats max_value_u64")?),
201 null_count: u64::from_le_bytes(to_array!(data[32..40], "stats null_count")?),
202 value_count: u64::from_le_bytes(to_array!(data[40..48], "stats value_count")?),
203 sum: f64::from_le_bytes(to_array!(data[48..56], "stats sum")?),
204 sum_i128: i128::from_le_bytes(to_array!(data[56..72], "stats sum_i128")?),
205 distinct_count: u32::from_le_bytes(to_array!(data[72..76], "stats distinct_count")?),
206 })
207 }
208}
209
210pub const COLUMN_STATS_SIZE: usize = 76;
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn stats_roundtrip() {
219 let mut stats = ColumnStats::empty();
220 stats.update_i64(10);
221 stats.update_i64(20);
222 stats.update_i64(30);
223
224 let bytes = stats.to_bytes();
225 assert_eq!(bytes.len(), COLUMN_STATS_SIZE);
226 let recovered = ColumnStats::from_bytes(&bytes).unwrap();
227 assert_eq!(stats, recovered);
228 }
229
230 #[test]
231 fn stats_i64_tracking() {
232 let mut stats = ColumnStats::empty();
233 stats.update_i64(-5);
234 stats.update_i64(10);
235 stats.update_i64(3);
236
237 assert_eq!(stats.min_value, -5);
238 assert_eq!(stats.max_value, 10);
239 assert_eq!(stats.value_count, 3);
240 assert!((stats.sum - 8.0).abs() < f64::EPSILON);
241 assert_eq!(stats.sum_i128, 8);
242 }
243
244 #[test]
245 fn stats_f64_tracking() {
246 let mut stats = ColumnStats::empty();
247 stats.update_f64(1.5);
248 stats.update_f64(2.5);
249 stats.update_f64(f64::NAN);
250
251 assert_eq!(stats.value_count, 3);
252 assert!((stats.sum - 4.0).abs() < f64::EPSILON);
254 }
255
256 #[test]
257 fn stats_f64_negative_ordering() {
258 let mut stats = ColumnStats::empty();
259 stats.update_f64(-10.0);
260 stats.update_f64(-1.0);
261 stats.update_f64(5.0);
262
263 let min_f = ordered_i64_to_f64(stats.min_value);
265 let max_f = ordered_i64_to_f64(stats.max_value);
266 assert!(
267 (min_f - (-10.0)).abs() < f64::EPSILON,
268 "min should be -10.0, got {min_f}"
269 );
270 assert!(
271 (max_f - 5.0).abs() < f64::EPSILON,
272 "max should be 5.0, got {max_f}"
273 );
274 }
275
276 #[test]
277 fn f64_total_ordering() {
278 let values = [
280 f64::NEG_INFINITY,
281 -1e100,
282 -1.0,
283 -f64::MIN_POSITIVE,
284 -0.0,
285 0.0,
286 f64::MIN_POSITIVE,
287 1.0,
288 1e100,
289 f64::INFINITY,
290 ];
291 for window in values.windows(2) {
292 let a = f64_to_ordered_i64(window[0]);
293 let b = f64_to_ordered_i64(window[1]);
294 assert!(
295 a <= b,
296 "ordering violated: {:?} (={a}) should <= {:?} (={b})",
297 window[0],
298 window[1]
299 );
300 }
301 }
302
303 #[test]
304 fn stats_u64_large_values() {
305 let mut stats = ColumnStats::empty();
306 stats.update_u64(u64::MAX);
307 stats.update_u64(0);
308
309 assert_eq!(stats.max_value_u64, u64::MAX);
311 assert_eq!(stats.min_value_u64, 0);
312 assert_eq!(stats.sum_i128, u64::MAX as i128);
314 }
315
316 #[test]
317 fn stats_u64_above_i64_max() {
318 let mut stats = ColumnStats::empty();
319 let big = (i64::MAX as u64) + 100;
320 stats.update_u64(big);
321 stats.update_u64(42);
322
323 assert_eq!(stats.max_value_u64, big);
324 assert_eq!(stats.min_value_u64, 42);
325 assert_eq!(stats.value_count, 2);
326 assert_eq!(stats.sum_i128, big as i128 + 42);
327 }
328
329 #[test]
330 fn stats_from_bytes_too_short() {
331 assert!(ColumnStats::from_bytes(&[0u8; 10]).is_err());
332 }
333
334 #[test]
335 fn stats_null_tracking() {
336 let mut stats = ColumnStats::empty();
337 stats.update_i64(42);
338 stats.record_null();
339 stats.record_null();
340
341 assert_eq!(stats.value_count, 1);
342 assert_eq!(stats.null_count, 2);
343 }
344
345 #[test]
346 fn stats_empty() {
347 let stats = ColumnStats::empty();
348 assert_eq!(stats.min_value, i64::MAX);
349 assert_eq!(stats.max_value, i64::MIN);
350 assert_eq!(stats.value_count, 0);
351 assert_eq!(stats.null_count, 0);
352 }
353
354 #[test]
355 fn stats_f64_nan_excluded_from_min_max() {
356 let mut stats = ColumnStats::empty();
357 stats.update_f64(f64::NAN);
358 assert_eq!(stats.min_value, i64::MAX, "NaN corrupted min_value");
360 assert_eq!(stats.max_value, i64::MIN, "NaN corrupted max_value");
361 assert_eq!(stats.value_count, 1);
362
363 stats.update_f64(42.0);
365 stats.update_f64(f64::NAN);
366 let max_f = ordered_i64_to_f64(stats.max_value);
367 assert!(
368 (max_f - 42.0).abs() < f64::EPSILON,
369 "max should be 42.0, got {max_f}"
370 );
371 }
372
373 #[test]
374 fn stats_bool_tracking() {
375 let mut stats = ColumnStats::empty();
376 stats.update_bool(false);
377 stats.update_bool(true);
378 stats.update_bool(false);
379
380 assert_eq!(stats.min_value, 0);
381 assert_eq!(stats.max_value, 1);
382 assert_eq!(stats.value_count, 3);
383 }
384
385 #[test]
386 fn stats_f64_infinity() {
387 let mut stats = ColumnStats::empty();
388 stats.update_f64(f64::NEG_INFINITY);
389 stats.update_f64(f64::INFINITY);
390
391 let min_f = ordered_i64_to_f64(stats.min_value);
392 let max_f = ordered_i64_to_f64(stats.max_value);
393 assert!(min_f.is_infinite() && min_f.is_sign_negative());
394 assert!(max_f.is_infinite() && max_f.is_sign_positive());
395 assert!(stats.sum.abs() < f64::EPSILON);
397 }
398
399 #[test]
400 fn stats_i128_precision_beyond_f64() {
401 let mut stats = ColumnStats::empty();
404 let big: i64 = (1_i64 << 53) + 1; stats.update_i64(big);
406 stats.update_i64(big);
407
408 let expected_i128 = 2 * (big as i128);
410 assert_eq!(stats.sum_i128, expected_i128);
411
412 let bytes = stats.to_bytes();
414 let recovered = ColumnStats::from_bytes(&bytes).unwrap();
415 assert_eq!(recovered.sum_i128, expected_i128);
416 }
417}