1use std::io::{self, Write};
13
14use byteorder::{LittleEndian, WriteBytesExt};
15
16use super::{bitpack_read, bitpack_write, bits_needed_u64};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22#[repr(u8)]
23pub enum CodecType {
24 Constant = 0,
25 Bitpacked = 1,
26 Linear = 2,
27 BlockwiseLinear = 3,
28}
29
30impl CodecType {
31 pub fn from_u8(v: u8) -> Option<Self> {
32 match v {
33 0 => Some(Self::Constant),
34 1 => Some(Self::Bitpacked),
35 2 => Some(Self::Linear),
36 3 => Some(Self::BlockwiseLinear),
37 _ => None,
38 }
39 }
40}
41
42pub const BLOCKWISE_LINEAR_BLOCK_SIZE: usize = 512;
44
45pub trait CodecEstimator {
52 fn collect(&mut self, value: u64);
53 fn finalize(&mut self) {}
54 fn estimate(&self) -> Option<u64>;
55 fn serialize(&self, values: &[u64], writer: &mut dyn Write) -> io::Result<u64>;
56}
57
58#[derive(Default)]
62pub struct ConstantEstimator {
63 first: Option<u64>,
64 all_same: bool,
65}
66
67impl CodecEstimator for ConstantEstimator {
68 fn collect(&mut self, value: u64) {
69 match self.first {
70 None => {
71 self.first = Some(value);
72 self.all_same = true;
73 }
74 Some(f) => {
75 if value != f {
76 self.all_same = false;
77 }
78 }
79 }
80 }
81
82 fn estimate(&self) -> Option<u64> {
83 if self.all_same {
84 Some(9)
86 } else {
87 None
88 }
89 }
90
91 fn serialize(&self, values: &[u64], writer: &mut dyn Write) -> io::Result<u64> {
92 let val = if values.is_empty() { 0 } else { values[0] };
93 writer.write_u8(CodecType::Constant as u8)?;
94 writer.write_u64::<LittleEndian>(val)?;
95 Ok(9)
96 }
97}
98
99#[derive(Default)]
103pub struct BitpackedEstimator {
104 min: u64,
105 max: u64,
106 count: usize,
107 initialized: bool,
108}
109
110impl CodecEstimator for BitpackedEstimator {
111 fn collect(&mut self, value: u64) {
112 if !self.initialized {
113 self.min = value;
114 self.max = value;
115 self.initialized = true;
116 } else {
117 self.min = self.min.min(value);
118 self.max = self.max.max(value);
119 }
120 self.count += 1;
121 }
122
123 fn estimate(&self) -> Option<u64> {
124 if self.count == 0 {
125 return Some(0);
126 }
127 let range = self.max - self.min;
128 let bpv = bits_needed_u64(range) as u64;
129 let data_bits = self.count as u64 * bpv;
131 let data_bytes = data_bits.div_ceil(8);
132 Some(1 + 8 + 1 + data_bytes)
133 }
134
135 fn serialize(&self, values: &[u64], writer: &mut dyn Write) -> io::Result<u64> {
136 let (min_value, bpv) = if values.is_empty() {
137 (0u64, 0u8)
138 } else {
139 let min_val = values.iter().copied().min().unwrap();
140 let max_val = values.iter().copied().max().unwrap();
141 (min_val, bits_needed_u64(max_val - min_val))
142 };
143
144 writer.write_u8(CodecType::Bitpacked as u8)?;
145 writer.write_u64::<LittleEndian>(min_value)?;
146 writer.write_u8(bpv)?;
147 let mut bytes_written = 10u64; if bpv > 0 && !values.is_empty() {
150 let shifted: Vec<u64> = values.iter().map(|&v| v - min_value).collect();
151 let mut packed = Vec::new();
152 bitpack_write(&shifted, bpv, &mut packed);
153 writer.write_all(&packed)?;
154 bytes_written += packed.len() as u64;
155 }
156 Ok(bytes_written)
157 }
158}
159
160#[inline]
164pub fn bitpacked_read(data: &[u8], index: usize) -> u64 {
165 let min_value = u64::from_le_bytes(data[0..8].try_into().unwrap());
166 let bpv = data[8];
167 if bpv == 0 {
168 return min_value;
169 }
170 let packed = &data[9..];
171 bitpack_read(packed, bpv, index) + min_value
172}
173
174#[derive(Default)]
188pub struct LinearEstimator {
189 count: usize,
190 first: u64,
191 last: u64,
192 min_val: u64,
193 max_val: u64,
194 min_residual: i64,
195 max_residual: i64,
196 values_collected: bool,
197 overflow: bool,
199}
200
201impl CodecEstimator for LinearEstimator {
202 fn collect(&mut self, value: u64) {
203 if !self.values_collected {
204 self.first = value;
205 self.min_val = value;
206 self.max_val = value;
207 self.values_collected = true;
208 } else {
209 self.min_val = self.min_val.min(value);
210 self.max_val = self.max_val.max(value);
211 }
212 self.last = value;
213 self.count += 1;
214 }
215
216 fn finalize(&mut self) {
217 if self.count < 2 {
218 return;
219 }
220 let pred_min = self.first.min(self.last) as i128;
228 let pred_max = self.first.max(self.last) as i128;
229 let min_res = self.min_val as i128 - pred_max;
230 let max_res = self.max_val as i128 - pred_min;
231 if min_res < i64::MIN as i128 || max_res > i64::MAX as i128 {
234 self.overflow = true;
235 return;
236 }
237 self.min_residual = min_res as i64;
238 self.max_residual = max_res as i64;
239 }
240
241 fn estimate(&self) -> Option<u64> {
242 if self.count < 2 || self.overflow {
243 return None;
244 }
245 let range = (self.max_residual as i128 - self.min_residual as i128) as u64;
247 let bpv = bits_needed_u64(range) as u64;
248 let data_bits = self.count as u64 * bpv;
249 let data_bytes = data_bits.div_ceil(8);
250 Some(1 + 8 + 8 + 4 + 8 + 1 + data_bytes)
252 }
253
254 fn serialize(&self, values: &[u64], writer: &mut dyn Write) -> io::Result<u64> {
255 let n = values.len();
256 if n < 2 {
257 return Err(io::Error::new(
258 io::ErrorKind::InvalidInput,
259 "linear needs ≥ 2 values",
260 ));
261 }
262 let first = values[0];
263 let last = values[n - 1];
264
265 let mut min_residual = i128::MAX;
267 for (i, &val) in values.iter().enumerate() {
268 let predicted = interpolate(first, last, n, i);
269 let residual = val as i128 - predicted as i128;
270 min_residual = min_residual.min(residual);
271 }
272
273 if min_residual < i64::MIN as i128 || min_residual > i64::MAX as i128 {
275 return Err(io::Error::new(
276 io::ErrorKind::InvalidInput,
277 "linear codec: residual offset exceeds i64 range",
278 ));
279 }
280 let min_residual_i64 = min_residual as i64;
281
282 let shifted: Vec<u64> = values
284 .iter()
285 .enumerate()
286 .map(|(i, &val)| {
287 let predicted = interpolate(first, last, n, i);
288 let residual = val as i128 - predicted as i128;
289 (residual - min_residual) as u64
290 })
291 .collect();
292 let max_shifted = shifted.iter().copied().max().unwrap_or(0);
293 let bpv = bits_needed_u64(max_shifted);
294 writer.write_u8(CodecType::Linear as u8)?;
295 writer.write_u64::<LittleEndian>(first)?;
296 writer.write_u64::<LittleEndian>(last)?;
297 writer.write_u32::<LittleEndian>(n as u32)?;
298 writer.write_i64::<LittleEndian>(min_residual_i64)?;
299 writer.write_u8(bpv)?;
300 let mut bytes_written = 30u64; if bpv > 0 {
303 let mut packed = Vec::new();
304 bitpack_write(&shifted, bpv, &mut packed);
305 writer.write_all(&packed)?;
306 bytes_written += packed.len() as u64;
307 }
308
309 Ok(bytes_written)
310 }
311}
312
313#[inline]
315fn interpolate(first: u64, last: u64, n: usize, i: usize) -> u64 {
316 if n <= 1 {
317 return first;
318 }
319 let first = first as i128;
321 let last = last as i128;
322 let n = n as i128;
323 let i = i as i128;
324 let result = first + (last - first) * i / (n - 1);
325 result as u64
326}
327
328#[inline]
332pub fn linear_read(data: &[u8], index: usize) -> u64 {
333 let first = u64::from_le_bytes(data[0..8].try_into().unwrap());
334 let last = u64::from_le_bytes(data[8..16].try_into().unwrap());
335 let n = u32::from_le_bytes(data[16..20].try_into().unwrap()) as usize;
336 let offset = i64::from_le_bytes(data[20..28].try_into().unwrap());
337 let bpv = data[28];
338 let predicted = interpolate(first, last, n, index);
339 let residual = if bpv == 0 {
340 0u64
341 } else {
342 bitpack_read(&data[29..], bpv, index)
343 };
344 (predicted as i128 + offset as i128 + residual as i128) as u64
346}
347
348#[derive(Default)]
355pub struct BlockwiseLinearEstimator {
356 values: Vec<u64>,
357}
358
359impl CodecEstimator for BlockwiseLinearEstimator {
360 fn collect(&mut self, value: u64) {
361 self.values.push(value);
362 }
363
364 fn estimate(&self) -> Option<u64> {
365 let n = self.values.len();
366 if n < 2 * BLOCKWISE_LINEAR_BLOCK_SIZE {
367 return None;
369 }
370
371 let num_blocks = n.div_ceil(BLOCKWISE_LINEAR_BLOCK_SIZE);
372 let mut total = 9u64; for b in 0..num_blocks {
376 let start = b * BLOCKWISE_LINEAR_BLOCK_SIZE;
377 let end = (start + BLOCKWISE_LINEAR_BLOCK_SIZE).min(n);
378 let block = &self.values[start..end];
379 let block_len = block.len();
380
381 if block_len < 2 {
382 total += 29; continue;
385 }
386
387 let first = block[0];
388 let last = block[block_len - 1];
389 let mut min_res = i128::MAX;
390 let mut max_res = i128::MIN;
391 for (i, &val) in block.iter().enumerate() {
392 let pred = interpolate(first, last, block_len, i);
393 let res = val as i128 - pred as i128;
394 min_res = min_res.min(res);
395 max_res = max_res.max(res);
396 }
397 if min_res < i64::MIN as i128 || max_res > i64::MAX as i128 {
400 return None;
401 }
402 let range = (max_res - min_res) as u64;
403 let bpv = bits_needed_u64(range) as u64;
404 let data_bits = block_len as u64 * bpv;
405 let data_bytes = data_bits.div_ceil(8);
406 total += 29 + data_bytes;
407 }
408
409 Some(total)
410 }
411
412 fn serialize(&self, values: &[u64], writer: &mut dyn Write) -> io::Result<u64> {
413 let n = values.len();
414 let num_blocks = n.div_ceil(BLOCKWISE_LINEAR_BLOCK_SIZE);
415
416 writer.write_u8(CodecType::BlockwiseLinear as u8)?;
417 writer.write_u32::<LittleEndian>(n as u32)?;
418 writer.write_u32::<LittleEndian>(num_blocks as u32)?;
419 let mut bytes_written = 9u64;
420
421 for b in 0..num_blocks {
422 let start = b * BLOCKWISE_LINEAR_BLOCK_SIZE;
423 let end = (start + BLOCKWISE_LINEAR_BLOCK_SIZE).min(n);
424 let block = &values[start..end];
425 let block_len = block.len();
426
427 let first = block[0];
428 let last = if block_len > 1 {
429 block[block_len - 1]
430 } else {
431 first
432 };
433
434 let mut min_residual = i128::MAX;
436 if block_len >= 2 {
437 for (i, &val) in block.iter().enumerate() {
438 let pred = interpolate(first, last, block_len, i);
439 let res = val as i128 - pred as i128;
440 min_residual = min_residual.min(res);
441 }
442 } else {
443 min_residual = 0;
444 }
445
446 let shifted: Vec<u64> = block
447 .iter()
448 .enumerate()
449 .map(|(i, &val)| {
450 if block_len < 2 {
451 return 0;
452 }
453 let pred = interpolate(first, last, block_len, i);
454 let res = val as i128 - pred as i128;
455 (res - min_residual) as u64
456 })
457 .collect();
458 let max_shifted = shifted.iter().copied().max().unwrap_or(0);
459 let bpv = bits_needed_u64(max_shifted);
460
461 if min_residual < i64::MIN as i128 || min_residual > i64::MAX as i128 {
463 return Err(io::Error::new(
464 io::ErrorKind::InvalidInput,
465 "blockwise linear codec: per-block residual offset exceeds i64 range",
466 ));
467 }
468 let min_res_i64 = min_residual as i64;
469 writer.write_u64::<LittleEndian>(first)?;
470 writer.write_u64::<LittleEndian>(last)?;
471 writer.write_i64::<LittleEndian>(min_res_i64)?;
472 writer.write_u8(bpv)?;
473
474 let mut packed = Vec::new();
475 if bpv > 0 {
476 bitpack_write(&shifted, bpv, &mut packed);
477 }
478 writer.write_u32::<LittleEndian>(packed.len() as u32)?;
479 writer.write_all(&packed)?;
480 bytes_written += 29 + packed.len() as u64;
481 }
482
483 Ok(bytes_written)
484 }
485}
486
487pub fn blockwise_linear_read(data: &[u8], index: usize) -> u64 {
491 let _num_values = u32::from_le_bytes(data[0..4].try_into().unwrap()) as usize;
492 let num_blocks = u32::from_le_bytes(data[4..8].try_into().unwrap()) as usize;
493
494 let target_block = index / BLOCKWISE_LINEAR_BLOCK_SIZE;
495 let index_in_block = index % BLOCKWISE_LINEAR_BLOCK_SIZE;
496
497 let mut pos = 8usize;
499 for b in 0..num_blocks {
500 let first = u64::from_le_bytes(data[pos..pos + 8].try_into().unwrap());
501 let last = u64::from_le_bytes(data[pos + 8..pos + 16].try_into().unwrap());
502 let offset = i64::from_le_bytes(data[pos + 16..pos + 24].try_into().unwrap());
503 let bpv = data[pos + 24];
504 let packed_len = u32::from_le_bytes(data[pos + 25..pos + 29].try_into().unwrap()) as usize;
505
506 if b == target_block {
507 let block_start = b * BLOCKWISE_LINEAR_BLOCK_SIZE;
508 let block_end = ((b + 1) * BLOCKWISE_LINEAR_BLOCK_SIZE).min(_num_values);
509 let block_len = block_end - block_start;
510
511 let predicted = interpolate(first, last, block_len, index_in_block);
512 let residual = if bpv == 0 {
513 0u64
514 } else {
515 bitpack_read(&data[pos + 29..], bpv, index_in_block)
516 };
517 return (predicted as i128 + offset as i128 + residual as i128) as u64;
518 }
519
520 pos += 29 + packed_len;
521 }
522
523 0 }
525
526pub fn serialize_auto(values: &[u64], writer: &mut dyn Write) -> io::Result<u64> {
532 let mut constant = ConstantEstimator::default();
533 let mut bitpacked = BitpackedEstimator::default();
534 let mut linear = LinearEstimator::default();
535 let mut blockwise = BlockwiseLinearEstimator::default();
536
537 for &v in values {
539 constant.collect(v);
540 bitpacked.collect(v);
541 linear.collect(v);
542 blockwise.collect(v);
543 }
544
545 constant.finalize();
547 bitpacked.finalize();
548 linear.finalize();
549 blockwise.finalize();
550
551 let candidates: Vec<(&dyn CodecEstimator, &str)> = vec![
553 (&constant, "constant"),
554 (&bitpacked, "bitpacked"),
555 (&linear, "linear"),
556 (&blockwise, "blockwise_linear"),
557 ];
558
559 let (best, _name) = candidates
560 .into_iter()
561 .filter_map(|(est, name)| est.estimate().map(|size| (est, name, size)))
562 .min_by_key(|&(_, _, size)| size)
563 .map(|(est, name, _)| (est, name))
564 .unwrap_or((&bitpacked as &dyn CodecEstimator, "bitpacked"));
565
566 best.serialize(values, writer)
567}
568
569pub fn bitpacked_read_batch(data: &[u8], start_index: usize, out: &mut [u64]) {
576 let min_value = u64::from_le_bytes(data[0..8].try_into().unwrap());
577 let bpv = data[8];
578
579 if bpv == 0 {
580 out.iter_mut().for_each(|v| *v = min_value);
581 return;
582 }
583
584 let packed = &data[9..];
585
586 match bpv {
587 8 => {
589 for (i, v) in out.iter_mut().enumerate() {
590 let idx = start_index + i;
591 *v = packed[idx] as u64 + min_value;
592 }
593 }
594 16 => {
595 for (i, v) in out.iter_mut().enumerate() {
596 let idx = start_index + i;
597 let byte_off = idx * 2;
598 let raw = u16::from_le_bytes([packed[byte_off], packed[byte_off + 1]]);
599 *v = raw as u64 + min_value;
600 }
601 }
602 32 => {
603 for (i, v) in out.iter_mut().enumerate() {
604 let idx = start_index + i;
605 let byte_off = idx * 4;
606 let raw = u32::from_le_bytes(packed[byte_off..byte_off + 4].try_into().unwrap());
607 *v = raw as u64 + min_value;
608 }
609 }
610 64 => {
611 for (i, v) in out.iter_mut().enumerate() {
612 let idx = start_index + i;
613 let byte_off = idx * 8;
614 let raw = u64::from_le_bytes(packed[byte_off..byte_off + 8].try_into().unwrap());
615 *v = raw.wrapping_add(min_value);
616 }
617 }
618 _ => {
620 for (i, v) in out.iter_mut().enumerate() {
621 *v = super::bitpack_read(packed, bpv, start_index + i) + min_value;
622 }
623 }
624 }
625}
626
627pub fn auto_read_batch(data: &[u8], start_index: usize, out: &mut [u64]) {
632 if data.is_empty() || out.is_empty() {
633 out.iter_mut().for_each(|v| *v = 0);
634 return;
635 }
636 let codec_id = data[0];
637 let rest = &data[1..];
638 match CodecType::from_u8(codec_id) {
639 Some(CodecType::Constant) => {
640 let val = u64::from_le_bytes(rest[0..8].try_into().unwrap());
641 out.iter_mut().for_each(|v| *v = val);
642 }
643 Some(CodecType::Bitpacked) => bitpacked_read_batch(rest, start_index, out),
644 Some(CodecType::Linear) => {
645 for (i, v) in out.iter_mut().enumerate() {
646 *v = linear_read(rest, start_index + i);
647 }
648 }
649 Some(CodecType::BlockwiseLinear) => {
650 for (i, v) in out.iter_mut().enumerate() {
651 *v = blockwise_linear_read(rest, start_index + i);
652 }
653 }
654 None => out.iter_mut().for_each(|v| *v = 0),
655 }
656}
657
658#[inline]
662pub fn auto_read(data: &[u8], index: usize) -> u64 {
663 if data.is_empty() {
664 return 0;
665 }
666 let codec_id = data[0];
667 let rest = &data[1..];
668 match CodecType::from_u8(codec_id) {
669 Some(CodecType::Constant) => {
670 u64::from_le_bytes(rest[0..8].try_into().unwrap())
672 }
673 Some(CodecType::Bitpacked) => bitpacked_read(rest, index),
674 Some(CodecType::Linear) => linear_read(rest, index),
675 Some(CodecType::BlockwiseLinear) => blockwise_linear_read(rest, index),
676 None => 0,
677 }
678}
679
680#[cfg(test)]
683mod tests {
684 use super::*;
685
686 fn roundtrip(values: &[u64]) -> Vec<u64> {
687 let mut buf = Vec::new();
688 serialize_auto(values, &mut buf).unwrap();
689 (0..values.len()).map(|i| auto_read(&buf, i)).collect()
690 }
691
692 #[test]
693 fn test_constant_codec() {
694 let values: Vec<u64> = vec![42; 100];
695 let mut buf = Vec::new();
696 serialize_auto(&values, &mut buf).unwrap();
697 assert_eq!(buf[0], CodecType::Constant as u8);
698 assert_eq!(buf.len(), 9);
699 assert_eq!(roundtrip(&values), values);
700 }
701
702 #[test]
703 fn test_bitpacked_codec() {
704 let values: Vec<u64> = (0..50).map(|i| 1000 + (i % 7) * 13).collect();
705 let result = roundtrip(&values);
706 assert_eq!(result, values);
707 }
708
709 #[test]
710 fn test_linear_codec_sequential() {
711 let values: Vec<u64> = (0..1000).map(|i| 100 + i * 3).collect();
713 let mut buf = Vec::new();
714 serialize_auto(&values, &mut buf).unwrap();
715 assert_eq!(roundtrip(&values), values);
717 }
718
719 #[test]
720 fn test_blockwise_linear_codec() {
721 let mut values: Vec<u64> = Vec::new();
723 for i in 0..1500 {
724 if i < 750 {
725 values.push(100 + i * 2);
726 } else {
727 values.push(5000 + (i - 750) * 5);
728 }
729 }
730 let result = roundtrip(&values);
731 assert_eq!(result, values);
732 }
733
734 #[test]
735 fn test_empty() {
736 let values: Vec<u64> = vec![];
737 let mut buf = Vec::new();
738 serialize_auto(&values, &mut buf).unwrap();
739 assert!(buf.len() <= 10);
740 }
741
742 #[test]
743 fn test_single_value() {
744 let values = vec![999u64];
745 assert_eq!(roundtrip(&values), values);
746 }
747
748 #[test]
749 fn test_two_values() {
750 let values = vec![10u64, 20];
751 assert_eq!(roundtrip(&values), values);
752 }
753
754 #[test]
755 fn test_large_range() {
756 let values = vec![0u64, u64::MAX / 2, u64::MAX];
757 assert_eq!(roundtrip(&values), values);
758 }
759
760 #[test]
761 fn test_timestamps_pick_linear_or_blockwise() {
762 let mut values: Vec<u64> = Vec::new();
764 let mut ts = 1_700_000_000u64;
765 for _ in 0..2000 {
766 values.push(ts);
767 ts += 1000 + (ts % 7); }
769 let result = roundtrip(&values);
770 assert_eq!(result, values);
771 }
772
773 fn roundtrip_batch(values: &[u64]) {
775 let mut buf = Vec::new();
776 serialize_auto(values, &mut buf).unwrap();
777
778 let mut batch_out = vec![0u64; values.len()];
780 auto_read_batch(&buf, 0, &mut batch_out);
781 assert_eq!(batch_out, values, "batch read mismatch");
782
783 if values.len() >= 10 {
785 let start = 3;
786 let count = values.len() - 6;
787 let mut sub = vec![0u64; count];
788 auto_read_batch(&buf, start, &mut sub);
789 assert_eq!(
790 sub,
791 &values[start..start + count],
792 "sub-range batch mismatch"
793 );
794 }
795 }
796
797 #[test]
798 fn test_batch_read_constant() {
799 roundtrip_batch(&vec![42u64; 100]);
800 }
801
802 #[test]
803 fn test_batch_read_bitpacked_8bit() {
804 let values: Vec<u64> = (0..200).map(|i| 1000 + (i % 200)).collect();
806 roundtrip_batch(&values);
807 }
808
809 #[test]
810 fn test_batch_read_bitpacked_16bit() {
811 let values: Vec<u64> = (0..200).map(|i| 50000 + i * 100).collect();
813 roundtrip_batch(&values);
814 }
815
816 #[test]
817 fn test_batch_read_bitpacked_arbitrary() {
818 let values: Vec<u64> = (0..100).map(|i| 999 + (i * 37) % 8000).collect();
820 roundtrip_batch(&values);
821 }
822
823 #[test]
824 fn test_batch_read_linear() {
825 let values: Vec<u64> = (0..500).map(|i| 100 + i * 3).collect();
826 roundtrip_batch(&values);
827 }
828
829 #[test]
830 fn test_batch_read_blockwise() {
831 let mut values = Vec::new();
832 for i in 0..1500u64 {
833 values.push(if i < 750 {
834 100 + i * 2
835 } else {
836 5000 + (i - 750) * 5
837 });
838 }
839 roundtrip_batch(&values);
840 }
841
842 #[test]
846 fn test_zigzag_timestamps_with_missing() {
847 use super::super::{FAST_FIELD_MISSING, zigzag_encode};
848
849 let timestamps: Vec<i64> = vec![
851 1724630400, 1724716800, 1724803200, 1700000000, 1680000000, 1724630400, ];
858
859 let mut values = Vec::new();
861 for (i, &ts) in timestamps.iter().enumerate() {
862 values.push(zigzag_encode(ts));
863 if i % 2 == 1 {
865 values.push(FAST_FIELD_MISSING);
866 }
867 }
868
869 let result = roundtrip(&values);
870 assert_eq!(
871 result, values,
872 "zigzag timestamps + missing roundtrip failed"
873 );
874 }
875
876 #[test]
878 fn test_codecs_individually_with_zigzag_and_missing() {
879 use super::super::{FAST_FIELD_MISSING, zigzag_encode};
880
881 let values: Vec<u64> = vec![
882 zigzag_encode(1724630400), zigzag_encode(1700000000), FAST_FIELD_MISSING,
885 zigzag_encode(1680000000), zigzag_encode(1724716800), FAST_FIELD_MISSING,
888 zigzag_encode(1724630400), zigzag_encode(0), ];
891
892 {
894 let mut est = BitpackedEstimator::default();
895 for &v in &values {
896 est.collect(v);
897 }
898 est.finalize();
899 if est.estimate().is_some() {
900 let mut buf = Vec::new();
901 est.serialize(&values, &mut buf).unwrap();
902 for (i, &expected) in values.iter().enumerate() {
903 let got = auto_read(&buf, i);
904 assert_eq!(
905 got, expected,
906 "bitpacked: index {} expected {} got {}",
907 i, expected, got
908 );
909 }
910 }
911 }
912
913 {
915 let mut est = LinearEstimator::default();
916 for &v in &values {
917 est.collect(v);
918 }
919 est.finalize();
920 if est.estimate().is_some() {
921 let mut buf = Vec::new();
922 est.serialize(&values, &mut buf).unwrap();
923 for (i, &expected) in values.iter().enumerate() {
924 let got = auto_read(&buf, i);
925 assert_eq!(
926 got, expected,
927 "linear: index {} expected {} got {}",
928 i, expected, got
929 );
930 }
931 }
932 }
933
934 let result = roundtrip(&values);
936 assert_eq!(result, values, "auto codec roundtrip failed");
937 }
938
939 #[test]
941 fn test_specific_issued_at_roundtrip() {
942 use super::super::{FAST_FIELD_MISSING, zigzag_encode};
943
944 let mut values = Vec::new();
946 let base_ts = 1724630400i64; for i in 0..100u64 {
948 if i % 5 == 0 {
949 values.push(FAST_FIELD_MISSING);
951 } else {
952 let ts = base_ts - (i as i64 * 86400); values.push(zigzag_encode(ts));
955 }
956 }
957
958 let result = roundtrip(&values);
959 for (i, (&expected, &got)) in values.iter().zip(result.iter()).enumerate() {
960 assert_eq!(
961 got,
962 expected,
963 "doc {}: expected {} (zigzag of {}), got {}",
964 i,
965 expected,
966 if expected == FAST_FIELD_MISSING {
967 -1 } else {
969 super::super::zigzag_decode(expected)
970 },
971 got
972 );
973 }
974 }
975
976 #[test]
979 fn test_large_scale_timestamp_roundtrip() {
980 use super::super::{FAST_FIELD_MISSING, zigzag_encode};
981
982 for num_docs in [10_000, 50_000, 100_000] {
983 let mut values = Vec::with_capacity(num_docs);
984 let base_ts = 1724630400i64;
985
986 for i in 0..num_docs {
987 if i % 7 == 0 {
988 values.push(FAST_FIELD_MISSING);
989 } else {
990 let ts = base_ts - (i as i64 * 3600) + ((i as i64 * 37) % 1000);
992 values.push(zigzag_encode(ts));
993 }
994 }
995
996 let mut buf = Vec::new();
998 serialize_auto(&values, &mut buf).unwrap();
999 let codec_id = buf[0];
1000 let codec_name = match CodecType::from_u8(codec_id) {
1001 Some(CodecType::Constant) => "constant",
1002 Some(CodecType::Bitpacked) => "bitpacked",
1003 Some(CodecType::Linear) => "linear",
1004 Some(CodecType::BlockwiseLinear) => "blockwise_linear",
1005 None => "unknown",
1006 };
1007
1008 let mut failures = Vec::new();
1010 for (i, &expected) in values.iter().enumerate() {
1011 let got = auto_read(&buf, i);
1012 if got != expected {
1013 failures.push((i, expected, got));
1014 if failures.len() >= 5 {
1015 break;
1016 }
1017 }
1018 }
1019
1020 assert!(
1021 failures.is_empty(),
1022 "num_docs={}, codec={}: {} failures. First 5: {:?}",
1023 num_docs,
1024 codec_name,
1025 failures.len(),
1026 failures
1027 );
1028 }
1029 }
1030
1031 #[test]
1035 fn test_blockwise_linear_with_clustered_missing() {
1036 use super::super::{FAST_FIELD_MISSING, zigzag_encode};
1037
1038 let mut values = Vec::new();
1043
1044 for i in 0..512 {
1047 if i < 100 {
1048 values.push(FAST_FIELD_MISSING);
1049 } else {
1050 let ts = 1724630400i64 + (i as i64 * 100);
1051 values.push(zigzag_encode(ts));
1052 }
1053 }
1054
1055 for i in 512..3072 {
1057 let ts = 1724630400i64 + (i as i64 * 100);
1058 values.push(zigzag_encode(ts));
1059 }
1060
1061 let result = roundtrip(&values);
1062 let mut failures = Vec::new();
1063 for (i, (&expected, &got)) in values.iter().zip(result.iter()).enumerate() {
1064 if got != expected {
1065 failures.push((i, expected, got));
1066 }
1067 }
1068 assert!(
1069 failures.is_empty(),
1070 "blockwise linear with clustered missing: {} failures. First 5: {:?}",
1071 failures.len(),
1072 &failures[..failures.len().min(5)]
1073 );
1074 }
1075
1076 #[test]
1079 fn test_forced_codecs_with_timestamps_and_missing() {
1080 use super::super::{FAST_FIELD_MISSING, zigzag_encode};
1081
1082 let mut values = Vec::new();
1083 let base_ts = 1724630400i64;
1084 for i in 0..200 {
1085 if i % 5 == 0 {
1086 values.push(FAST_FIELD_MISSING);
1087 } else {
1088 let ts = base_ts - (i as i64 * 86400);
1089 values.push(zigzag_encode(ts));
1090 }
1091 }
1092
1093 {
1095 let est = BitpackedEstimator::default();
1096 let mut buf = Vec::new();
1097 est.serialize(&values, &mut buf).unwrap();
1098 for (i, &expected) in values.iter().enumerate() {
1099 let got = bitpacked_read(&buf[1..], i); assert_eq!(got, expected, "forced bitpacked: index {} failed", i);
1101 }
1102 }
1103
1104 {
1107 let est = LinearEstimator::default();
1108 let mut buf = Vec::new();
1109 let result = est.serialize(&values, &mut buf);
1110 assert!(
1111 result.is_err(),
1112 "linear codec should reject data with residuals exceeding i64"
1113 );
1114 }
1115
1116 {
1118 let safe_values: Vec<u64> = values
1119 .iter()
1120 .filter(|&&v| v != FAST_FIELD_MISSING)
1121 .copied()
1122 .collect();
1123 let est = LinearEstimator::default();
1124 let mut buf = Vec::new();
1125 est.serialize(&safe_values, &mut buf).unwrap();
1126 for (i, &expected) in safe_values.iter().enumerate() {
1127 let got = linear_read(&buf[1..], i);
1128 assert_eq!(got, expected, "forced linear (safe): index {} failed", i);
1129 }
1130 }
1131
1132 {
1134 let mut large_values = Vec::new();
1135 for i in 0..2000 {
1136 if i % 5 == 0 {
1137 large_values.push(FAST_FIELD_MISSING);
1138 } else {
1139 let ts = base_ts - (i as i64 * 86400);
1140 large_values.push(zigzag_encode(ts));
1141 }
1142 }
1143 let mut est = BlockwiseLinearEstimator::default();
1144 for &v in &large_values {
1145 est.collect(v);
1146 }
1147 assert!(
1148 est.estimate().is_none(),
1149 "blockwise linear should reject data with per-block residuals exceeding i64"
1150 );
1151 }
1152 }
1153}