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(super) fn value_bounds(data: &[u8]) -> Option<(u64, u64)> {
48 let (&tag, data) = data.split_first()?;
49 let width_max = |bits: u8| u64::MAX.checked_shr(u32::from(64 - bits)).unwrap_or(0);
50 match CodecType::from_u8(tag)? {
51 CodecType::Constant => {
52 let value = u64::from_le_bytes(data[..8].try_into().unwrap());
53 Some((value, value))
54 }
55 CodecType::Bitpacked => {
56 let min = u64::from_le_bytes(data[..8].try_into().unwrap());
57 Some((min, min.checked_add(width_max(data[8]))?))
58 }
59 CodecType::Linear => {
60 let first = u64::from_le_bytes(data[..8].try_into().unwrap());
61 let last = u64::from_le_bytes(data[8..16].try_into().unwrap());
62 let offset = i64::from_le_bytes(data[20..28].try_into().unwrap()) as i128;
63 let min = i128::from(first.min(last)) + offset;
64 let max = i128::from(first.max(last)) + offset + i128::from(width_max(data[28]));
65 Some((u64::try_from(min).ok()?, u64::try_from(max).ok()?))
66 }
67 CodecType::BlockwiseLinear => None,
68 }
69}
70
71pub fn validate_auto(data: &[u8], expected_values: usize) -> io::Result<()> {
75 let (&codec_id, rest) = data.split_first().ok_or_else(|| {
76 io::Error::new(io::ErrorKind::UnexpectedEof, "fast field codec is missing")
77 })?;
78
79 let packed_len = |count: usize, bpv: u8| -> io::Result<usize> {
80 if bpv > 64 {
81 return Err(io::Error::new(
82 io::ErrorKind::InvalidData,
83 "fast field bit width exceeds 64",
84 ));
85 }
86 count
87 .checked_mul(bpv as usize)
88 .and_then(|bits| bits.checked_add(7))
89 .map(|bits| bits / 8)
90 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "fast field size overflow"))
91 };
92
93 match CodecType::from_u8(codec_id) {
94 Some(CodecType::Constant) => {
95 if rest.len() != 8 {
96 return Err(io::Error::new(
97 io::ErrorKind::InvalidData,
98 "invalid constant fast field length",
99 ));
100 }
101 }
102 Some(CodecType::Bitpacked) => {
103 if rest.len() < 9 {
104 return Err(io::Error::new(
105 io::ErrorKind::UnexpectedEof,
106 "bitpacked fast field header is truncated",
107 ));
108 }
109 let expected_len = 9usize
110 .checked_add(packed_len(expected_values, rest[8])?)
111 .ok_or_else(|| {
112 io::Error::new(io::ErrorKind::InvalidData, "fast field size overflow")
113 })?;
114 if rest.len() != expected_len {
115 return Err(io::Error::new(
116 io::ErrorKind::InvalidData,
117 "bitpacked fast field length is inconsistent",
118 ));
119 }
120 }
121 Some(CodecType::Linear) => {
122 if rest.len() < 29 {
123 return Err(io::Error::new(
124 io::ErrorKind::UnexpectedEof,
125 "linear fast field header is truncated",
126 ));
127 }
128 let count = u32::from_le_bytes(rest[16..20].try_into().unwrap()) as usize;
129 if count != expected_values || count < 2 {
130 return Err(io::Error::new(
131 io::ErrorKind::InvalidData,
132 "linear fast field value count is inconsistent",
133 ));
134 }
135 let expected_len = 29usize
136 .checked_add(packed_len(count, rest[28])?)
137 .ok_or_else(|| {
138 io::Error::new(io::ErrorKind::InvalidData, "fast field size overflow")
139 })?;
140 if rest.len() != expected_len {
141 return Err(io::Error::new(
142 io::ErrorKind::InvalidData,
143 "linear fast field length is inconsistent",
144 ));
145 }
146 }
147 Some(CodecType::BlockwiseLinear) => {
148 if rest.len() < 8 {
149 return Err(io::Error::new(
150 io::ErrorKind::UnexpectedEof,
151 "blockwise fast field header is truncated",
152 ));
153 }
154 let count = u32::from_le_bytes(rest[0..4].try_into().unwrap()) as usize;
155 let num_blocks = u32::from_le_bytes(rest[4..8].try_into().unwrap()) as usize;
156 if count != expected_values || num_blocks != count.div_ceil(BLOCKWISE_LINEAR_BLOCK_SIZE)
157 {
158 return Err(io::Error::new(
159 io::ErrorKind::InvalidData,
160 "blockwise fast field counts are inconsistent",
161 ));
162 }
163
164 let mut pos = 8usize;
165 for block_idx in 0..num_blocks {
166 let header_end = pos.checked_add(29).ok_or_else(|| {
167 io::Error::new(io::ErrorKind::InvalidData, "fast field offset overflow")
168 })?;
169 if header_end > rest.len() {
170 return Err(io::Error::new(
171 io::ErrorKind::UnexpectedEof,
172 "blockwise fast field block header is truncated",
173 ));
174 }
175 let bpv = rest[pos + 24];
176 let declared =
177 u32::from_le_bytes(rest[pos + 25..header_end].try_into().unwrap()) as usize;
178 let block_start = block_idx * BLOCKWISE_LINEAR_BLOCK_SIZE;
179 let block_count = (count - block_start).min(BLOCKWISE_LINEAR_BLOCK_SIZE);
180 let expected = packed_len(block_count, bpv)?;
181 if declared != expected {
182 return Err(io::Error::new(
183 io::ErrorKind::InvalidData,
184 "blockwise fast field packed length is inconsistent",
185 ));
186 }
187 pos = header_end.checked_add(declared).ok_or_else(|| {
188 io::Error::new(io::ErrorKind::InvalidData, "fast field offset overflow")
189 })?;
190 if pos > rest.len() {
191 return Err(io::Error::new(
192 io::ErrorKind::UnexpectedEof,
193 "blockwise fast field data is truncated",
194 ));
195 }
196 }
197 if pos != rest.len() {
198 return Err(io::Error::new(
199 io::ErrorKind::InvalidData,
200 "blockwise fast field contains trailing data",
201 ));
202 }
203 }
204 None => {
205 return Err(io::Error::new(
206 io::ErrorKind::InvalidData,
207 "unknown fast field codec",
208 ));
209 }
210 }
211 Ok(())
212}
213
214pub trait CodecEstimator {
221 fn collect(&mut self, value: u64);
222 fn finalize(&mut self) {}
223 fn estimate(&self) -> Option<u64>;
224 fn serialize(&self, values: &[u64], writer: &mut dyn Write) -> io::Result<u64>;
225}
226
227#[derive(Default)]
231pub struct ConstantEstimator {
232 first: Option<u64>,
233 all_same: bool,
234}
235
236impl CodecEstimator for ConstantEstimator {
237 fn collect(&mut self, value: u64) {
238 match self.first {
239 None => {
240 self.first = Some(value);
241 self.all_same = true;
242 }
243 Some(f) => {
244 if value != f {
245 self.all_same = false;
246 }
247 }
248 }
249 }
250
251 fn estimate(&self) -> Option<u64> {
252 if self.all_same {
253 Some(9)
255 } else {
256 None
257 }
258 }
259
260 fn serialize(&self, values: &[u64], writer: &mut dyn Write) -> io::Result<u64> {
261 let val = if values.is_empty() { 0 } else { values[0] };
262 writer.write_u8(CodecType::Constant as u8)?;
263 writer.write_u64::<LittleEndian>(val)?;
264 Ok(9)
265 }
266}
267
268#[derive(Default)]
272pub struct BitpackedEstimator {
273 min: u64,
274 max: u64,
275 count: usize,
276 initialized: bool,
277}
278
279impl CodecEstimator for BitpackedEstimator {
280 fn collect(&mut self, value: u64) {
281 if !self.initialized {
282 self.min = value;
283 self.max = value;
284 self.initialized = true;
285 } else {
286 self.min = self.min.min(value);
287 self.max = self.max.max(value);
288 }
289 self.count += 1;
290 }
291
292 fn estimate(&self) -> Option<u64> {
293 if self.count == 0 {
294 return Some(0);
295 }
296 let range = self.max - self.min;
297 let bpv = bits_needed_u64(range) as u64;
298 let data_bits = self.count as u64 * bpv;
300 let data_bytes = data_bits.div_ceil(8);
301 Some(1 + 8 + 1 + data_bytes)
302 }
303
304 fn serialize(&self, values: &[u64], writer: &mut dyn Write) -> io::Result<u64> {
305 let (min_value, bpv) = if values.is_empty() {
306 (0u64, 0u8)
307 } else {
308 let min_val = values.iter().copied().min().unwrap();
309 let max_val = values.iter().copied().max().unwrap();
310 (min_val, bits_needed_u64(max_val - min_val))
311 };
312
313 writer.write_u8(CodecType::Bitpacked as u8)?;
314 writer.write_u64::<LittleEndian>(min_value)?;
315 writer.write_u8(bpv)?;
316 let mut bytes_written = 10u64; if bpv > 0 && !values.is_empty() {
319 let shifted: Vec<u64> = values.iter().map(|&v| v - min_value).collect();
320 let mut packed = Vec::new();
321 bitpack_write(&shifted, bpv, &mut packed);
322 writer.write_all(&packed)?;
323 bytes_written += packed.len() as u64;
324 }
325 Ok(bytes_written)
326 }
327}
328
329#[inline]
333pub fn bitpacked_read(data: &[u8], index: usize) -> u64 {
334 let min_value = u64::from_le_bytes(data[0..8].try_into().unwrap());
335 let bpv = data[8];
336 if bpv == 0 {
337 return min_value;
338 }
339 let packed = &data[9..];
340 bitpack_read(packed, bpv, index).wrapping_add(min_value)
341}
342
343#[derive(Default)]
357pub struct LinearEstimator {
358 count: usize,
359 first: u64,
360 last: u64,
361 min_val: u64,
362 max_val: u64,
363 min_residual: i64,
364 max_residual: i64,
365 values_collected: bool,
366 overflow: bool,
368}
369
370impl CodecEstimator for LinearEstimator {
371 fn collect(&mut self, value: u64) {
372 if !self.values_collected {
373 self.first = value;
374 self.min_val = value;
375 self.max_val = value;
376 self.values_collected = true;
377 } else {
378 self.min_val = self.min_val.min(value);
379 self.max_val = self.max_val.max(value);
380 }
381 self.last = value;
382 self.count += 1;
383 }
384
385 fn finalize(&mut self) {
386 if self.count < 2 {
387 return;
388 }
389 let pred_min = self.first.min(self.last) as i128;
397 let pred_max = self.first.max(self.last) as i128;
398 let min_res = self.min_val as i128 - pred_max;
399 let max_res = self.max_val as i128 - pred_min;
400 if min_res < i64::MIN as i128 || max_res > i64::MAX as i128 {
403 self.overflow = true;
404 return;
405 }
406 self.min_residual = min_res as i64;
407 self.max_residual = max_res as i64;
408 }
409
410 fn estimate(&self) -> Option<u64> {
411 if self.count < 2 || self.overflow {
412 return None;
413 }
414 let range = (self.max_residual as i128 - self.min_residual as i128) as u64;
416 let bpv = bits_needed_u64(range) as u64;
417 let data_bits = self.count as u64 * bpv;
418 let data_bytes = data_bits.div_ceil(8);
419 Some(1 + 8 + 8 + 4 + 8 + 1 + data_bytes)
421 }
422
423 fn serialize(&self, values: &[u64], writer: &mut dyn Write) -> io::Result<u64> {
424 let n = values.len();
425 if n < 2 {
426 return Err(io::Error::new(
427 io::ErrorKind::InvalidInput,
428 "linear needs ≥ 2 values",
429 ));
430 }
431 let first = values[0];
432 let last = values[n - 1];
433
434 let mut min_residual = i128::MAX;
436 for (i, &val) in values.iter().enumerate() {
437 let predicted = interpolate(first, last, n, i);
438 let residual = val as i128 - predicted as i128;
439 min_residual = min_residual.min(residual);
440 }
441
442 if min_residual < i64::MIN as i128 || min_residual > i64::MAX as i128 {
444 return Err(io::Error::new(
445 io::ErrorKind::InvalidInput,
446 "linear codec: residual offset exceeds i64 range",
447 ));
448 }
449 let min_residual_i64 = min_residual as i64;
450
451 let shifted: Vec<u64> = values
453 .iter()
454 .enumerate()
455 .map(|(i, &val)| {
456 let predicted = interpolate(first, last, n, i);
457 let residual = val as i128 - predicted as i128;
458 (residual - min_residual) as u64
459 })
460 .collect();
461 let max_shifted = shifted.iter().copied().max().unwrap_or(0);
462 let bpv = bits_needed_u64(max_shifted);
463 writer.write_u8(CodecType::Linear as u8)?;
464 writer.write_u64::<LittleEndian>(first)?;
465 writer.write_u64::<LittleEndian>(last)?;
466 writer.write_u32::<LittleEndian>(n as u32)?;
467 writer.write_i64::<LittleEndian>(min_residual_i64)?;
468 writer.write_u8(bpv)?;
469 let mut bytes_written = 30u64; if bpv > 0 {
472 let mut packed = Vec::new();
473 bitpack_write(&shifted, bpv, &mut packed);
474 writer.write_all(&packed)?;
475 bytes_written += packed.len() as u64;
476 }
477
478 Ok(bytes_written)
479 }
480}
481
482#[inline]
484fn interpolate(first: u64, last: u64, n: usize, i: usize) -> u64 {
485 if n <= 1 {
486 return first;
487 }
488 let first = first as i128;
490 let last = last as i128;
491 let n = n as i128;
492 let i = i as i128;
493 let result = first + (last - first) * i / (n - 1);
494 result as u64
495}
496
497#[inline]
501pub fn linear_read(data: &[u8], index: usize) -> u64 {
502 let first = u64::from_le_bytes(data[0..8].try_into().unwrap());
503 let last = u64::from_le_bytes(data[8..16].try_into().unwrap());
504 let n = u32::from_le_bytes(data[16..20].try_into().unwrap()) as usize;
505 let offset = i64::from_le_bytes(data[20..28].try_into().unwrap());
506 let bpv = data[28];
507 let predicted = interpolate(first, last, n, index);
508 let residual = if bpv == 0 {
509 0u64
510 } else {
511 bitpack_read(&data[29..], bpv, index)
512 };
513 (predicted as i128 + offset as i128 + residual as i128) as u64
515}
516
517#[derive(Clone, Copy)]
524struct BlockwiseLinearBlockEstimate {
525 min_residual: i64,
526 bits_per_value: u8,
527 serialized_size: u64,
528}
529
530#[derive(Default)]
531pub struct BlockwiseLinearEstimator {
532 count: usize,
533 completed_size: u64,
534 current_block: Vec<u64>,
535 completed_blocks: Vec<BlockwiseLinearBlockEstimate>,
536 tail_estimate: Option<BlockwiseLinearBlockEstimate>,
537 overflow: bool,
538}
539
540impl BlockwiseLinearEstimator {
541 fn collect_values(&mut self, values: &[u64]) {
547 debug_assert_eq!(self.count, 0);
548 debug_assert!(self.current_block.is_empty());
549
550 self.count = values.len();
551 let mut blocks = values.chunks_exact(BLOCKWISE_LINEAR_BLOCK_SIZE);
552 for block in &mut blocks {
553 match estimate_blockwise_linear_block(block) {
554 Some(estimate) => {
555 self.completed_size += estimate.serialized_size;
556 self.completed_blocks.push(estimate);
557 }
558 None => {
559 self.overflow = true;
560 return;
561 }
562 }
563 }
564 self.current_block.extend_from_slice(blocks.remainder());
565 }
566}
567
568fn estimate_blockwise_linear_block(block: &[u64]) -> Option<BlockwiseLinearBlockEstimate> {
574 debug_assert!(!block.is_empty());
575 let block_len = block.len();
576 if block_len < 2 {
577 return Some(BlockwiseLinearBlockEstimate {
578 min_residual: 0,
579 bits_per_value: 0,
580 serialized_size: 29,
581 });
582 }
583
584 let first = block[0];
585 let last = block[block_len - 1];
586 let mut min_res = i128::MAX;
587 let mut max_res = i128::MIN;
588 for (i, &val) in block.iter().enumerate() {
589 let pred = interpolate(first, last, block_len, i);
590 let res = val as i128 - pred as i128;
591 min_res = min_res.min(res);
592 max_res = max_res.max(res);
593 }
594
595 if min_res < i64::MIN as i128 || max_res > i64::MAX as i128 {
598 return None;
599 }
600
601 let bits_per_value = bits_needed_u64((max_res - min_res) as u64);
602 let data_bytes = (block_len as u64 * u64::from(bits_per_value)).div_ceil(8);
603 Some(BlockwiseLinearBlockEstimate {
604 min_residual: min_res as i64,
605 bits_per_value,
606 serialized_size: 29 + data_bytes,
607 })
608}
609
610impl CodecEstimator for BlockwiseLinearEstimator {
611 fn collect(&mut self, value: u64) {
612 self.count += 1;
613 self.tail_estimate = None;
614 if self.overflow {
615 return;
616 }
617
618 self.current_block.push(value);
619 if self.current_block.len() == BLOCKWISE_LINEAR_BLOCK_SIZE {
620 match estimate_blockwise_linear_block(&self.current_block) {
621 Some(estimate) => {
622 self.completed_size += estimate.serialized_size;
623 self.completed_blocks.push(estimate);
624 }
625 None => self.overflow = true,
626 }
627 self.current_block.clear();
628 }
629 }
630
631 fn finalize(&mut self) {
632 self.tail_estimate = if self.current_block.is_empty() || self.overflow {
633 None
634 } else {
635 estimate_blockwise_linear_block(&self.current_block)
636 };
637 if !self.current_block.is_empty() && self.tail_estimate.is_none() {
638 self.overflow = true;
639 }
640 }
641
642 fn estimate(&self) -> Option<u64> {
643 if self.count < 2 * BLOCKWISE_LINEAR_BLOCK_SIZE || self.overflow {
644 return None;
646 }
647
648 let mut total = 9 + self.completed_size;
650 if !self.current_block.is_empty() {
651 total += self
652 .tail_estimate
653 .or_else(|| estimate_blockwise_linear_block(&self.current_block))?
654 .serialized_size;
655 }
656 Some(total)
657 }
658
659 fn serialize(&self, values: &[u64], writer: &mut dyn Write) -> io::Result<u64> {
660 let n = values.len();
661 let num_blocks = n.div_ceil(BLOCKWISE_LINEAR_BLOCK_SIZE);
662
663 writer.write_u8(CodecType::BlockwiseLinear as u8)?;
664 writer.write_u32::<LittleEndian>(n as u32)?;
665 writer.write_u32::<LittleEndian>(num_blocks as u32)?;
666 let mut bytes_written = 9u64;
667
668 let mut shifted = Vec::new();
671 let mut packed = Vec::new();
672 let estimates_match = self.count == n
673 && self.completed_blocks.len() == n / BLOCKWISE_LINEAR_BLOCK_SIZE
674 && (n.is_multiple_of(BLOCKWISE_LINEAR_BLOCK_SIZE) || self.tail_estimate.is_some());
675
676 for b in 0..num_blocks {
677 let start = b * BLOCKWISE_LINEAR_BLOCK_SIZE;
678 let end = (start + BLOCKWISE_LINEAR_BLOCK_SIZE).min(n);
679 let block = &values[start..end];
680 let block_len = block.len();
681
682 let first = block[0];
683 let last = if block_len > 1 {
684 block[block_len - 1]
685 } else {
686 first
687 };
688
689 let estimate = if estimates_match {
690 self.completed_blocks.get(b).copied().or(self.tail_estimate)
691 } else {
692 None
693 };
694 let estimate = match estimate {
695 Some(estimate) => estimate,
696 None => estimate_blockwise_linear_block(block).ok_or_else(|| {
697 io::Error::new(
698 io::ErrorKind::InvalidInput,
699 "blockwise linear codec: per-block residual offset exceeds i64 range",
700 )
701 })?,
702 };
703 let min_residual = i128::from(estimate.min_residual);
704
705 shifted.clear();
706 shifted.extend(block.iter().enumerate().map(|(i, &val)| {
707 if block_len < 2 {
708 0
709 } else {
710 let pred = interpolate(first, last, block_len, i);
711 let res = val as i128 - pred as i128;
712 (res - min_residual) as u64
713 }
714 }));
715 writer.write_u64::<LittleEndian>(first)?;
716 writer.write_u64::<LittleEndian>(last)?;
717 writer.write_i64::<LittleEndian>(estimate.min_residual)?;
718 writer.write_u8(estimate.bits_per_value)?;
719
720 packed.clear();
721 if estimate.bits_per_value > 0 {
722 bitpack_write(&shifted, estimate.bits_per_value, &mut packed);
723 }
724 writer.write_u32::<LittleEndian>(packed.len() as u32)?;
725 writer.write_all(&packed)?;
726 bytes_written += 29 + packed.len() as u64;
727 }
728
729 Ok(bytes_written)
730 }
731}
732
733pub fn blockwise_linear_read(data: &[u8], index: usize) -> u64 {
737 blockwise_linear_read_from(data, index, 0, 8)
738}
739
740pub(super) fn blockwise_linear_read_from(
742 data: &[u8],
743 index: usize,
744 first_block: usize,
745 mut pos: usize,
746) -> u64 {
747 let _num_values = u32::from_le_bytes(data[0..4].try_into().unwrap()) as usize;
748 let num_blocks = u32::from_le_bytes(data[4..8].try_into().unwrap()) as usize;
749 let target_block = index / BLOCKWISE_LINEAR_BLOCK_SIZE;
750 let index_in_block = index % BLOCKWISE_LINEAR_BLOCK_SIZE;
751 for b in first_block..num_blocks {
752 let first = u64::from_le_bytes(data[pos..pos + 8].try_into().unwrap());
753 let last = u64::from_le_bytes(data[pos + 8..pos + 16].try_into().unwrap());
754 let offset = i64::from_le_bytes(data[pos + 16..pos + 24].try_into().unwrap());
755 let bpv = data[pos + 24];
756 let packed_len = u32::from_le_bytes(data[pos + 25..pos + 29].try_into().unwrap()) as usize;
757
758 if b == target_block {
759 let block_start = b * BLOCKWISE_LINEAR_BLOCK_SIZE;
760 let block_end = ((b + 1) * BLOCKWISE_LINEAR_BLOCK_SIZE).min(_num_values);
761 let block_len = block_end - block_start;
762
763 let predicted = interpolate(first, last, block_len, index_in_block);
764 let residual = if bpv == 0 {
765 0u64
766 } else {
767 bitpack_read(&data[pos + 29..], bpv, index_in_block)
768 };
769 return (predicted as i128 + offset as i128 + residual as i128) as u64;
770 }
771
772 pos += 29 + packed_len;
773 }
774
775 0 }
777
778pub fn blockwise_linear_read_batch(data: &[u8], start_index: usize, out: &mut [u64]) {
784 blockwise_linear_read_batch_with_cursor(
785 data,
786 start_index,
787 out,
788 &mut BlockwiseLinearCursor::default(),
789 );
790}
791
792pub(super) struct BlockwiseLinearCursor {
795 block: usize,
796 offset: usize,
797}
798
799impl Default for BlockwiseLinearCursor {
800 fn default() -> Self {
801 Self {
802 block: 0,
803 offset: 8,
804 }
805 }
806}
807
808fn blockwise_linear_read_batch_with_cursor(
809 data: &[u8],
810 start_index: usize,
811 out: &mut [u64],
812 cursor: &mut BlockwiseLinearCursor,
813) {
814 if out.is_empty() {
815 return;
816 }
817
818 let num_values = u32::from_le_bytes(data[0..4].try_into().unwrap()) as usize;
819 let num_blocks = u32::from_le_bytes(data[4..8].try_into().unwrap()) as usize;
820 let valid_len = out.len().min(num_values.saturating_sub(start_index));
821 out[valid_len..].fill(0);
822 if valid_len == 0 {
823 return;
824 }
825
826 let target_block = start_index / BLOCKWISE_LINEAR_BLOCK_SIZE;
827 if target_block < cursor.block {
828 *cursor = BlockwiseLinearCursor::default();
829 }
830 let mut pos = cursor.offset;
831 let mut written = 0usize;
832
833 for block_idx in cursor.block..num_blocks {
834 let packed_len = u32::from_le_bytes(data[pos + 25..pos + 29].try_into().unwrap()) as usize;
835 if block_idx < target_block {
836 pos += 29 + packed_len;
837 continue;
838 }
839
840 let first = u64::from_le_bytes(data[pos..pos + 8].try_into().unwrap());
841 let last = u64::from_le_bytes(data[pos + 8..pos + 16].try_into().unwrap());
842 let offset = i64::from_le_bytes(data[pos + 16..pos + 24].try_into().unwrap());
843 let bpv = data[pos + 24];
844 let packed = &data[pos + 29..pos + 29 + packed_len];
845
846 let block_start = block_idx * BLOCKWISE_LINEAR_BLOCK_SIZE;
847 let block_len = (num_values - block_start).min(BLOCKWISE_LINEAR_BLOCK_SIZE);
848 let index_in_block = if block_idx == target_block {
849 start_index - block_start
850 } else {
851 0
852 };
853 let take = (block_len - index_in_block).min(valid_len - written);
854
855 for (i, value) in out[written..written + take].iter_mut().enumerate() {
856 let block_index = index_in_block + i;
857 let predicted = interpolate(first, last, block_len, block_index);
858 let residual = if bpv == 0 {
859 0
860 } else {
861 bitpack_read(packed, bpv, block_index)
862 };
863 *value = (predicted as i128 + offset as i128 + residual as i128) as u64;
864 }
865
866 written += take;
867 if index_in_block + take == block_len {
868 cursor.block = block_idx + 1;
869 cursor.offset = pos + 29 + packed_len;
870 } else {
871 cursor.block = block_idx;
872 cursor.offset = pos;
873 }
874 if written == valid_len {
875 break;
876 }
877 pos += 29 + packed_len;
878 }
879}
880
881pub fn serialize_auto(values: &[u64], writer: &mut dyn Write) -> io::Result<u64> {
887 let mut constant = ConstantEstimator::default();
888 let mut bitpacked = BitpackedEstimator::default();
889 let mut linear = LinearEstimator::default();
890 let mut blockwise = BlockwiseLinearEstimator::default();
891
892 for &v in values {
894 constant.collect(v);
895 bitpacked.collect(v);
896 linear.collect(v);
897 }
898 blockwise.collect_values(values);
899
900 constant.finalize();
902 bitpacked.finalize();
903 linear.finalize();
904 blockwise.finalize();
905
906 let candidates: Vec<(&dyn CodecEstimator, &str)> = vec![
908 (&constant, "constant"),
909 (&bitpacked, "bitpacked"),
910 (&linear, "linear"),
911 (&blockwise, "blockwise_linear"),
912 ];
913
914 let (best, _name) = candidates
915 .into_iter()
916 .filter_map(|(est, name)| est.estimate().map(|size| (est, name, size)))
917 .min_by_key(|&(_, _, size)| size)
918 .map(|(est, name, _)| (est, name))
919 .unwrap_or((&bitpacked as &dyn CodecEstimator, "bitpacked"));
920
921 best.serialize(values, writer)
922}
923
924pub fn bitpacked_read_batch(data: &[u8], start_index: usize, out: &mut [u64]) {
931 let min_value = u64::from_le_bytes(data[0..8].try_into().unwrap());
932 let bpv = data[8];
933
934 if bpv == 0 {
935 out.iter_mut().for_each(|v| *v = min_value);
936 return;
937 }
938
939 let packed = &data[9..];
940
941 match bpv {
942 8 => {
945 decode_byte_aligned_batch::<1>(packed, start_index, out, min_value, |v| u64::from(v[0]))
946 }
947 16 => decode_byte_aligned_batch::<2>(packed, start_index, out, min_value, |v| {
948 u64::from(u16::from_le_bytes(v))
949 }),
950 32 => decode_byte_aligned_batch::<4>(packed, start_index, out, min_value, |v| {
951 u64::from(u32::from_le_bytes(v))
952 }),
953 64 => {
954 decode_byte_aligned_batch::<8>(packed, start_index, out, min_value, u64::from_le_bytes)
955 }
956 _ => {
958 for (i, v) in out.iter_mut().enumerate() {
959 *v = super::bitpack_read(packed, bpv, start_index + i).wrapping_add(min_value);
960 }
961 }
962 }
963}
964
965#[inline]
968fn decode_byte_aligned_batch<const WIDTH: usize>(
969 packed: &[u8],
970 start: usize,
971 out: &mut [u64],
972 min: u64,
973 decode: impl Fn([u8; WIDTH]) -> u64,
974) {
975 if out.is_empty() {
976 return;
977 }
978 let byte_start = start.checked_mul(WIDTH).expect("bitpacked start overflow");
979 let byte_len = out
980 .len()
981 .checked_mul(WIDTH)
982 .expect("bitpacked length overflow");
983 let bytes = &packed[byte_start..][..byte_len];
984 let (chunks, _) = bytes.as_chunks::<WIDTH>();
985 for (value, &chunk) in out.iter_mut().zip(chunks) {
986 *value = decode(chunk).wrapping_add(min);
987 }
988}
989
990pub fn auto_read_batch(data: &[u8], start_index: usize, out: &mut [u64]) {
995 auto_read_batch_with_cursor(
996 data,
997 start_index,
998 out,
999 &mut BlockwiseLinearCursor::default(),
1000 );
1001}
1002
1003pub(super) fn auto_read_batch_with_cursor(
1006 data: &[u8],
1007 start_index: usize,
1008 out: &mut [u64],
1009 cursor: &mut BlockwiseLinearCursor,
1010) {
1011 if data.is_empty() || out.is_empty() {
1012 out.iter_mut().for_each(|v| *v = 0);
1013 return;
1014 }
1015 let codec_id = data[0];
1016 let rest = &data[1..];
1017 match CodecType::from_u8(codec_id) {
1018 Some(CodecType::Constant) => {
1019 let val = u64::from_le_bytes(rest[0..8].try_into().unwrap());
1020 out.iter_mut().for_each(|v| *v = val);
1021 }
1022 Some(CodecType::Bitpacked) => bitpacked_read_batch(rest, start_index, out),
1023 Some(CodecType::Linear) => {
1024 for (i, v) in out.iter_mut().enumerate() {
1025 *v = linear_read(rest, start_index + i);
1026 }
1027 }
1028 Some(CodecType::BlockwiseLinear) => {
1029 blockwise_linear_read_batch_with_cursor(rest, start_index, out, cursor)
1030 }
1031 None => out.iter_mut().for_each(|v| *v = 0),
1032 }
1033}
1034
1035#[inline]
1039pub fn auto_read(data: &[u8], index: usize) -> u64 {
1040 if data.is_empty() {
1041 return 0;
1042 }
1043 let codec_id = data[0];
1044 let rest = &data[1..];
1045 match CodecType::from_u8(codec_id) {
1046 Some(CodecType::Constant) => {
1047 u64::from_le_bytes(rest[0..8].try_into().unwrap())
1049 }
1050 Some(CodecType::Bitpacked) => bitpacked_read(rest, index),
1051 Some(CodecType::Linear) => linear_read(rest, index),
1052 Some(CodecType::BlockwiseLinear) => blockwise_linear_read(rest, index),
1053 None => 0,
1054 }
1055}
1056
1057#[cfg(test)]
1060mod tests {
1061 use super::*;
1062
1063 #[test]
1064 fn cursor_batches_preserve_all_codecs_boundaries_and_backward_reads() {
1065 let values = blockwise_values(2053);
1066 let mut constant = Vec::new();
1067 ConstantEstimator::default()
1068 .serialize(&[42; 2053], &mut constant)
1069 .unwrap();
1070 let mut bitpacked = Vec::new();
1071 let mut estimator = BitpackedEstimator::default();
1072 for &value in &values {
1073 estimator.collect(value);
1074 }
1075 estimator.finalize();
1076 estimator.serialize(&values, &mut bitpacked).unwrap();
1077 let descending: Vec<_> = (0..2053).map(|i| u64::MAX - i * 7).collect();
1078 let mut linear = Vec::new();
1079 let mut estimator = LinearEstimator::default();
1080 for &value in &descending {
1081 estimator.collect(value);
1082 }
1083 estimator.finalize();
1084 estimator.serialize(&descending, &mut linear).unwrap();
1085 let mut blockwise = Vec::new();
1086 BlockwiseLinearEstimator::default()
1087 .serialize(&values, &mut blockwise)
1088 .unwrap();
1089 assert_eq!(blockwise, serialize_blockwise_reference(&values));
1090 for (tag, encoded) in [constant, bitpacked, linear, blockwise].iter().enumerate() {
1091 assert_eq!(usize::from(encoded[0]), tag);
1092 validate_auto(encoded, 2053).unwrap();
1093 let expected: Vec<_> = (0..2053).map(|i| auto_read(encoded, i)).collect();
1094 for size in [1, 255, 256, 257, 511, 512, 513, 1000, 2053] {
1095 let mut cursor = BlockwiseLinearCursor::default();
1096 let mut actual = vec![0; 2053];
1097 for (batch, out) in actual.chunks_mut(size).enumerate() {
1098 auto_read_batch_with_cursor(encoded, batch * size, &mut [], &mut cursor);
1099 auto_read_batch_with_cursor(encoded, batch * size, out, &mut cursor);
1100 }
1101 assert_eq!(actual, expected, "codec {tag}, batch {size}");
1102 for start in [1023, 511, 0, 2048] {
1103 let mut out = [0; 5];
1104 auto_read_batch_with_cursor(encoded, start, &mut out, &mut cursor);
1105 assert_eq!(out, expected[start..start + 5]);
1106 }
1107 if tag == CodecType::BlockwiseLinear as usize {
1108 let mut out = [u64::MAX; 17];
1109 auto_read_batch_with_cursor(encoded, 2050, &mut out, &mut cursor);
1110 assert_eq!(&out[..3], &expected[2050..]);
1111 assert_eq!(&out[3..], &[0; 14]);
1112 auto_read_batch_with_cursor(encoded, usize::MAX, &mut out, &mut cursor);
1113 assert_eq!(out, [0; 17]);
1114 }
1115 }
1116 }
1117 }
1118
1119 #[test]
1120 fn header_bounds_contain_every_bitpacked_value_including_wrapping_payloads() {
1121 for bits in 0..=64 {
1122 let max = u64::MAX.checked_shr(64 - bits).unwrap_or(0);
1123 for min in [0u64, 17, u64::MAX - 17, u64::MAX] {
1124 let raw = [0, max / 2, max];
1125 let mut encoded = vec![CodecType::Bitpacked as u8];
1126 encoded.extend_from_slice(&min.to_le_bytes());
1127 encoded.push(bits as u8);
1128 bitpack_write(&raw, bits as u8, &mut encoded);
1129 validate_auto(&encoded, raw.len()).unwrap();
1130 let bounds = value_bounds(&encoded);
1131 if min.checked_add(max).is_none() {
1132 assert_eq!(bounds, None, "wrapping bounds must not prune");
1133 } else {
1134 assert_eq!(bounds, Some((min, min + max)));
1135 }
1136 for i in 0..raw.len() {
1137 let value = auto_read(&encoded, i);
1138 assert!(bounds.is_none_or(|(lo, hi)| value >= lo && value <= hi));
1139 }
1140 }
1141 }
1142 }
1143
1144 #[test]
1145 fn linear_header_bounds_preserve_descending_extreme_and_wrapping_values() {
1146 for first in [0u64, 100, u64::MAX - 100] {
1147 for last in [0u64, 100, u64::MAX - 100] {
1148 for offset in [i64::MIN, -17, 0, 17, i64::MAX] {
1149 for bits in [0u8, 1, 4, 64] {
1150 let max = u64::MAX.checked_shr(u32::from(64 - bits)).unwrap_or(0);
1151 let raw: Vec<_> =
1152 (0..17).map(|i| if i % 2 == 0 { max } else { 0 }).collect();
1153 let mut encoded = vec![CodecType::Linear as u8];
1154 encoded.extend_from_slice(&first.to_le_bytes());
1155 encoded.extend_from_slice(&last.to_le_bytes());
1156 encoded.extend_from_slice(&17u32.to_le_bytes());
1157 encoded.extend_from_slice(&offset.to_le_bytes());
1158 encoded.push(bits);
1159 bitpack_write(&raw, bits, &mut encoded);
1160 validate_auto(&encoded, raw.len()).unwrap();
1161 let bounds = value_bounds(&encoded);
1162 for i in 0..raw.len() {
1163 let value = auto_read(&encoded, i);
1164 assert!(bounds.is_none_or(|(lo, hi)| value >= lo && value <= hi));
1165 }
1166 }
1167 }
1168 }
1169 }
1170 let encoded = [CodecType::Constant as u8]
1171 .into_iter()
1172 .chain(u64::MAX.to_le_bytes())
1173 .collect::<Vec<_>>();
1174 assert_eq!(value_bounds(&encoded), Some((u64::MAX, u64::MAX)));
1175 }
1176
1177 fn roundtrip(values: &[u64]) -> Vec<u64> {
1178 let mut buf = Vec::new();
1179 serialize_auto(values, &mut buf).unwrap();
1180 (0..values.len()).map(|i| auto_read(&buf, i)).collect()
1181 }
1182
1183 fn blockwise_values(len: usize) -> Vec<u64> {
1184 (0..len)
1185 .map(|i| {
1186 let block = i / BLOCKWISE_LINEAR_BLOCK_SIZE;
1187 let index = i % BLOCKWISE_LINEAR_BLOCK_SIZE;
1188 block as u64 * 1_000_000
1189 + index as u64 * (block as u64 + 3)
1190 + ((i * 17 + block * 11) % 23) as u64
1191 })
1192 .collect()
1193 }
1194
1195 fn serialize_blockwise_reference(values: &[u64]) -> Vec<u8> {
1199 let n = values.len();
1200 let num_blocks = n.div_ceil(BLOCKWISE_LINEAR_BLOCK_SIZE);
1201 let mut encoded = Vec::new();
1202 encoded.push(CodecType::BlockwiseLinear as u8);
1203 encoded.extend_from_slice(&(n as u32).to_le_bytes());
1204 encoded.extend_from_slice(&(num_blocks as u32).to_le_bytes());
1205
1206 for block in values.chunks(BLOCKWISE_LINEAR_BLOCK_SIZE) {
1207 let block_len = block.len();
1208 let first = block[0];
1209 let last = if block_len > 1 {
1210 block[block_len - 1]
1211 } else {
1212 first
1213 };
1214 let min_residual = if block_len < 2 {
1215 0
1216 } else {
1217 block
1218 .iter()
1219 .enumerate()
1220 .map(|(i, &value)| {
1221 value as i128 - interpolate(first, last, block_len, i) as i128
1222 })
1223 .min()
1224 .unwrap()
1225 };
1226 let shifted: Vec<u64> = block
1227 .iter()
1228 .enumerate()
1229 .map(|(i, &value)| {
1230 if block_len < 2 {
1231 0
1232 } else {
1233 let predicted = interpolate(first, last, block_len, i);
1234 (value as i128 - predicted as i128 - min_residual) as u64
1235 }
1236 })
1237 .collect();
1238 let bpv = bits_needed_u64(shifted.iter().copied().max().unwrap_or(0));
1239 let mut packed = Vec::new();
1240 bitpack_write(&shifted, bpv, &mut packed);
1241
1242 encoded.extend_from_slice(&first.to_le_bytes());
1243 encoded.extend_from_slice(&last.to_le_bytes());
1244 encoded.extend_from_slice(&(min_residual as i64).to_le_bytes());
1245 encoded.push(bpv);
1246 encoded.extend_from_slice(&(packed.len() as u32).to_le_bytes());
1247 encoded.extend_from_slice(&packed);
1248 }
1249
1250 encoded
1251 }
1252
1253 #[test]
1254 fn test_constant_codec() {
1255 let values: Vec<u64> = vec![42; 100];
1256 let mut buf = Vec::new();
1257 serialize_auto(&values, &mut buf).unwrap();
1258 assert_eq!(buf[0], CodecType::Constant as u8);
1259 assert_eq!(buf.len(), 9);
1260 assert_eq!(roundtrip(&values), values);
1261 }
1262
1263 #[test]
1264 fn test_bitpacked_codec() {
1265 let values: Vec<u64> = (0..50).map(|i| 1000 + (i % 7) * 13).collect();
1266 let result = roundtrip(&values);
1267 assert_eq!(result, values);
1268 }
1269
1270 #[test]
1271 fn test_linear_codec_sequential() {
1272 let values: Vec<u64> = (0..1000).map(|i| 100 + i * 3).collect();
1274 let mut buf = Vec::new();
1275 serialize_auto(&values, &mut buf).unwrap();
1276 assert_eq!(roundtrip(&values), values);
1278 }
1279
1280 #[test]
1281 fn test_blockwise_linear_codec() {
1282 let mut values: Vec<u64> = Vec::new();
1284 for i in 0..1500 {
1285 if i < 750 {
1286 values.push(100 + i * 2);
1287 } else {
1288 values.push(5000 + (i - 750) * 5);
1289 }
1290 }
1291 let result = roundtrip(&values);
1292 assert_eq!(result, values);
1293 }
1294
1295 #[test]
1296 fn test_blockwise_estimate_is_bounded_and_serialization_is_byte_compatible() {
1297 for len in [1024, 1025, 1536, 1537, 4097] {
1298 let values = blockwise_values(len);
1299 let reference = serialize_blockwise_reference(&values);
1300 let mut estimator = BlockwiseLinearEstimator::default();
1301 for &value in &values {
1302 estimator.collect(value);
1303 }
1304 estimator.finalize();
1305
1306 assert_eq!(estimator.estimate(), Some(reference.len() as u64));
1307 assert!(
1308 estimator.current_block.len() < BLOCKWISE_LINEAR_BLOCK_SIZE,
1309 "estimator retained more than one partial block"
1310 );
1311 assert!(
1312 estimator.current_block.capacity() <= BLOCKWISE_LINEAR_BLOCK_SIZE,
1313 "estimator scratch grew beyond one block"
1314 );
1315
1316 let mut encoded = Vec::new();
1317 estimator.serialize(&values, &mut encoded).unwrap();
1318 assert_eq!(
1319 encoded, reference,
1320 "serialized bytes changed for {len} values"
1321 );
1322 }
1323 }
1324
1325 #[test]
1326 fn test_empty() {
1327 let values: Vec<u64> = vec![];
1328 let mut buf = Vec::new();
1329 serialize_auto(&values, &mut buf).unwrap();
1330 assert!(buf.len() <= 10);
1331 }
1332
1333 #[test]
1334 fn test_validate_rejects_truncated_and_inconsistent_payloads() {
1335 assert!(validate_auto(&[], 1).is_err());
1336 assert!(validate_auto(&[CodecType::Constant as u8], 1).is_err());
1337
1338 let mut bitpacked = vec![CodecType::Bitpacked as u8];
1339 bitpacked.extend_from_slice(&0u64.to_le_bytes());
1340 bitpacked.push(65);
1341 assert!(validate_auto(&bitpacked, 1).is_err());
1342
1343 let mut valid = Vec::new();
1344 serialize_auto(&[1, 2, 3, 4], &mut valid).unwrap();
1345 assert!(validate_auto(&valid, 4).is_ok());
1346 valid.pop();
1347 assert!(validate_auto(&valid, 4).is_err());
1348 }
1349
1350 #[test]
1351 fn test_single_value() {
1352 let values = vec![999u64];
1353 assert_eq!(roundtrip(&values), values);
1354 }
1355
1356 #[test]
1357 fn test_two_values() {
1358 let values = vec![10u64, 20];
1359 assert_eq!(roundtrip(&values), values);
1360 }
1361
1362 #[test]
1363 fn test_large_range() {
1364 let values = vec![0u64, u64::MAX / 2, u64::MAX];
1365 assert_eq!(roundtrip(&values), values);
1366 }
1367
1368 #[test]
1369 fn test_timestamps_pick_linear_or_blockwise() {
1370 let mut values: Vec<u64> = Vec::new();
1372 let mut ts = 1_700_000_000u64;
1373 for _ in 0..2000 {
1374 values.push(ts);
1375 ts += 1000 + (ts % 7); }
1377 let result = roundtrip(&values);
1378 assert_eq!(result, values);
1379 }
1380
1381 fn roundtrip_batch(values: &[u64]) {
1383 let mut buf = Vec::new();
1384 serialize_auto(values, &mut buf).unwrap();
1385
1386 let mut batch_out = vec![0u64; values.len()];
1388 auto_read_batch(&buf, 0, &mut batch_out);
1389 assert_eq!(batch_out, values, "batch read mismatch");
1390
1391 if values.len() >= 10 {
1393 let start = 3;
1394 let count = values.len() - 6;
1395 let mut sub = vec![0u64; count];
1396 auto_read_batch(&buf, start, &mut sub);
1397 assert_eq!(
1398 sub,
1399 &values[start..start + count],
1400 "sub-range batch mismatch"
1401 );
1402 }
1403 }
1404
1405 #[test]
1406 fn test_batch_read_constant() {
1407 roundtrip_batch(&vec![42u64; 100]);
1408 }
1409
1410 #[test]
1411 fn test_batch_read_bitpacked_8bit() {
1412 let values: Vec<u64> = (0..200).map(|i| 1000 + (i % 200)).collect();
1414 roundtrip_batch(&values);
1415 }
1416
1417 #[test]
1418 fn test_batch_read_bitpacked_16bit() {
1419 let values: Vec<u64> = (0..200).map(|i| 50000 + i * 100).collect();
1421 roundtrip_batch(&values);
1422 }
1423
1424 #[test]
1425 fn test_batch_read_bitpacked_arbitrary() {
1426 let values: Vec<u64> = (0..100).map(|i| 999 + (i * 37) % 8000).collect();
1428 roundtrip_batch(&values);
1429 }
1430
1431 #[test]
1432 fn byte_aligned_batches_preserve_offsets_tails_and_wrapping_values() {
1433 for bpv in [8u8, 16, 32, 64] {
1434 let min = u64::MAX - 11;
1435 let mut data = min.to_le_bytes().to_vec();
1436 data.push(bpv);
1437 let mut expected = Vec::new();
1438 for i in 0..521u64 {
1439 let raw = i.wrapping_mul(0x9e37_79b9_7f4a_7c15) >> (64 - bpv);
1440 data.extend_from_slice(&raw.to_le_bytes()[..usize::from(bpv / 8)]);
1441 expected.push(raw.wrapping_add(min));
1442 }
1443 for start in [0, 1, 3, 255, 256, 519, 521] {
1444 for len in [0, 1, 2, 7, 16, 255, 256, 257] {
1445 if start + len > expected.len() {
1446 continue;
1447 }
1448 let mut out = vec![0; len];
1449 bitpacked_read_batch(&data, start, &mut out);
1450 assert_eq!(
1451 out,
1452 expected[start..start + len],
1453 "bpv={bpv}, start={start}, len={len}"
1454 );
1455 }
1456 }
1457 bitpacked_read_batch(&data, usize::MAX, &mut []);
1459 }
1460 }
1461
1462 #[test]
1463 fn byte_aligned_batches_reject_truncated_input_instead_of_short_decoding() {
1464 for bpv in [8u8, 16, 32, 64] {
1465 let mut data = 0u64.to_le_bytes().to_vec();
1466 data.push(bpv);
1467 data.resize(9 + usize::from(bpv / 8) * 3 - 1, 0);
1468 assert!(
1469 std::panic::catch_unwind(|| {
1470 bitpacked_read_batch(&data, 0, &mut [0u64; 3]);
1471 })
1472 .is_err()
1473 );
1474 }
1475 }
1476
1477 #[test]
1478 fn test_batch_read_linear() {
1479 let values: Vec<u64> = (0..500).map(|i| 100 + i * 3).collect();
1480 roundtrip_batch(&values);
1481 }
1482
1483 #[test]
1484 fn test_batch_read_blockwise() {
1485 let mut values = Vec::new();
1486 for i in 0..1500u64 {
1487 values.push(if i < 750 {
1488 100 + i * 2
1489 } else {
1490 5000 + (i - 750) * 5
1491 });
1492 }
1493 roundtrip_batch(&values);
1494 }
1495
1496 #[test]
1497 fn test_blockwise_batch_read_across_block_boundaries() {
1498 let values = blockwise_values(2053);
1499 let estimator = BlockwiseLinearEstimator::default();
1500 let mut encoded = Vec::new();
1501 estimator.serialize(&values, &mut encoded).unwrap();
1502 assert_eq!(encoded[0], CodecType::BlockwiseLinear as u8);
1503
1504 for (start, len) in [
1505 (0, values.len()),
1506 (510, 5),
1507 (511, 3),
1508 (512, 513),
1509 (777, 900),
1510 (1023, 514),
1511 (1535, 518),
1512 (2048, 5),
1513 ] {
1514 let expected = &values[start..start + len];
1515
1516 let mut direct = vec![u64::MAX; len];
1517 blockwise_linear_read_batch(&encoded[1..], start, &mut direct);
1518 assert_eq!(direct, expected, "direct batch mismatch at {start}");
1519
1520 let mut auto = vec![u64::MAX; len];
1521 auto_read_batch(&encoded, start, &mut auto);
1522 assert_eq!(auto, expected, "auto batch mismatch at {start}");
1523 }
1524 }
1525
1526 #[test]
1530 fn test_zigzag_timestamps_with_missing() {
1531 use super::super::{FAST_FIELD_MISSING, zigzag_encode};
1532
1533 let timestamps: Vec<i64> = vec![
1535 1724630400, 1724716800, 1724803200, 1700000000, 1680000000, 1724630400, ];
1542
1543 let mut values = Vec::new();
1545 for (i, &ts) in timestamps.iter().enumerate() {
1546 values.push(zigzag_encode(ts));
1547 if i % 2 == 1 {
1549 values.push(FAST_FIELD_MISSING);
1550 }
1551 }
1552
1553 let result = roundtrip(&values);
1554 assert_eq!(
1555 result, values,
1556 "zigzag timestamps + missing roundtrip failed"
1557 );
1558 }
1559
1560 #[test]
1562 fn test_codecs_individually_with_zigzag_and_missing() {
1563 use super::super::{FAST_FIELD_MISSING, zigzag_encode};
1564
1565 let values: Vec<u64> = vec![
1566 zigzag_encode(1724630400), zigzag_encode(1700000000), FAST_FIELD_MISSING,
1569 zigzag_encode(1680000000), zigzag_encode(1724716800), FAST_FIELD_MISSING,
1572 zigzag_encode(1724630400), zigzag_encode(0), ];
1575
1576 {
1578 let mut est = BitpackedEstimator::default();
1579 for &v in &values {
1580 est.collect(v);
1581 }
1582 est.finalize();
1583 if est.estimate().is_some() {
1584 let mut buf = Vec::new();
1585 est.serialize(&values, &mut buf).unwrap();
1586 for (i, &expected) in values.iter().enumerate() {
1587 let got = auto_read(&buf, i);
1588 assert_eq!(
1589 got, expected,
1590 "bitpacked: index {} expected {} got {}",
1591 i, expected, got
1592 );
1593 }
1594 }
1595 }
1596
1597 {
1599 let mut est = LinearEstimator::default();
1600 for &v in &values {
1601 est.collect(v);
1602 }
1603 est.finalize();
1604 if est.estimate().is_some() {
1605 let mut buf = Vec::new();
1606 est.serialize(&values, &mut buf).unwrap();
1607 for (i, &expected) in values.iter().enumerate() {
1608 let got = auto_read(&buf, i);
1609 assert_eq!(
1610 got, expected,
1611 "linear: index {} expected {} got {}",
1612 i, expected, got
1613 );
1614 }
1615 }
1616 }
1617
1618 let result = roundtrip(&values);
1620 assert_eq!(result, values, "auto codec roundtrip failed");
1621 }
1622
1623 #[test]
1625 fn test_specific_issued_at_roundtrip() {
1626 use super::super::{FAST_FIELD_MISSING, zigzag_encode};
1627
1628 let mut values = Vec::new();
1630 let base_ts = 1724630400i64; for i in 0..100u64 {
1632 if i % 5 == 0 {
1633 values.push(FAST_FIELD_MISSING);
1635 } else {
1636 let ts = base_ts - (i as i64 * 86400); values.push(zigzag_encode(ts));
1639 }
1640 }
1641
1642 let result = roundtrip(&values);
1643 for (i, (&expected, &got)) in values.iter().zip(result.iter()).enumerate() {
1644 assert_eq!(
1645 got,
1646 expected,
1647 "doc {}: expected {} (zigzag of {}), got {}",
1648 i,
1649 expected,
1650 if expected == FAST_FIELD_MISSING {
1651 -1 } else {
1653 super::super::zigzag_decode(expected)
1654 },
1655 got
1656 );
1657 }
1658 }
1659
1660 #[test]
1663 fn test_large_scale_timestamp_roundtrip() {
1664 use super::super::{FAST_FIELD_MISSING, zigzag_encode};
1665
1666 for num_docs in [10_000, 50_000, 100_000] {
1667 let mut values = Vec::with_capacity(num_docs);
1668 let base_ts = 1724630400i64;
1669
1670 for i in 0..num_docs {
1671 if i % 7 == 0 {
1672 values.push(FAST_FIELD_MISSING);
1673 } else {
1674 let ts = base_ts - (i as i64 * 3600) + ((i as i64 * 37) % 1000);
1676 values.push(zigzag_encode(ts));
1677 }
1678 }
1679
1680 let mut buf = Vec::new();
1682 serialize_auto(&values, &mut buf).unwrap();
1683 let codec_id = buf[0];
1684 let codec_name = match CodecType::from_u8(codec_id) {
1685 Some(CodecType::Constant) => "constant",
1686 Some(CodecType::Bitpacked) => "bitpacked",
1687 Some(CodecType::Linear) => "linear",
1688 Some(CodecType::BlockwiseLinear) => "blockwise_linear",
1689 None => "unknown",
1690 };
1691
1692 let mut failures = Vec::new();
1694 for (i, &expected) in values.iter().enumerate() {
1695 let got = auto_read(&buf, i);
1696 if got != expected {
1697 failures.push((i, expected, got));
1698 if failures.len() >= 5 {
1699 break;
1700 }
1701 }
1702 }
1703
1704 assert!(
1705 failures.is_empty(),
1706 "num_docs={}, codec={}: {} failures. First 5: {:?}",
1707 num_docs,
1708 codec_name,
1709 failures.len(),
1710 failures
1711 );
1712 }
1713 }
1714
1715 #[test]
1719 fn test_blockwise_linear_with_clustered_missing() {
1720 use super::super::{FAST_FIELD_MISSING, zigzag_encode};
1721
1722 let mut values = Vec::new();
1727
1728 for i in 0..512 {
1731 if i < 100 {
1732 values.push(FAST_FIELD_MISSING);
1733 } else {
1734 let ts = 1724630400i64 + (i as i64 * 100);
1735 values.push(zigzag_encode(ts));
1736 }
1737 }
1738
1739 for i in 512..3072 {
1741 let ts = 1724630400i64 + (i as i64 * 100);
1742 values.push(zigzag_encode(ts));
1743 }
1744
1745 let result = roundtrip(&values);
1746 let mut failures = Vec::new();
1747 for (i, (&expected, &got)) in values.iter().zip(result.iter()).enumerate() {
1748 if got != expected {
1749 failures.push((i, expected, got));
1750 }
1751 }
1752 assert!(
1753 failures.is_empty(),
1754 "blockwise linear with clustered missing: {} failures. First 5: {:?}",
1755 failures.len(),
1756 &failures[..failures.len().min(5)]
1757 );
1758 }
1759
1760 #[test]
1763 fn test_forced_codecs_with_timestamps_and_missing() {
1764 use super::super::{FAST_FIELD_MISSING, zigzag_encode};
1765
1766 let mut values = Vec::new();
1767 let base_ts = 1724630400i64;
1768 for i in 0..200 {
1769 if i % 5 == 0 {
1770 values.push(FAST_FIELD_MISSING);
1771 } else {
1772 let ts = base_ts - (i as i64 * 86400);
1773 values.push(zigzag_encode(ts));
1774 }
1775 }
1776
1777 {
1779 let est = BitpackedEstimator::default();
1780 let mut buf = Vec::new();
1781 est.serialize(&values, &mut buf).unwrap();
1782 for (i, &expected) in values.iter().enumerate() {
1783 let got = bitpacked_read(&buf[1..], i); assert_eq!(got, expected, "forced bitpacked: index {} failed", i);
1785 }
1786 }
1787
1788 {
1791 let est = LinearEstimator::default();
1792 let mut buf = Vec::new();
1793 let result = est.serialize(&values, &mut buf);
1794 assert!(
1795 result.is_err(),
1796 "linear codec should reject data with residuals exceeding i64"
1797 );
1798 }
1799
1800 {
1802 let safe_values: Vec<u64> = values
1803 .iter()
1804 .filter(|&&v| v != FAST_FIELD_MISSING)
1805 .copied()
1806 .collect();
1807 let est = LinearEstimator::default();
1808 let mut buf = Vec::new();
1809 est.serialize(&safe_values, &mut buf).unwrap();
1810 for (i, &expected) in safe_values.iter().enumerate() {
1811 let got = linear_read(&buf[1..], i);
1812 assert_eq!(got, expected, "forced linear (safe): index {} failed", i);
1813 }
1814 }
1815
1816 {
1818 let mut large_values = Vec::new();
1819 for i in 0..2000 {
1820 if i % 5 == 0 {
1821 large_values.push(FAST_FIELD_MISSING);
1822 } else {
1823 let ts = base_ts - (i as i64 * 86400);
1824 large_values.push(zigzag_encode(ts));
1825 }
1826 }
1827 let mut est = BlockwiseLinearEstimator::default();
1828 for &v in &large_values {
1829 est.collect(v);
1830 }
1831 assert!(
1832 est.estimate().is_none(),
1833 "blockwise linear should reject data with per-block residuals exceeding i64"
1834 );
1835 }
1836 }
1837}