1use chronix_core::config::FloatEncoding;
18
19use crate::alp::AlpDecoder;
20use crate::bitmap::{BitmapDecoder, BitmapEncoder};
21use crate::chimp::{Chimp128Decoder, ChimpDecoder};
22use crate::delta::{DeltaOfDeltaDecoder, DeltaOfDeltaEncoder};
23use crate::dictionary::{DictionaryDecoder, DictionaryEncoder};
24use crate::error::{EncodingError, Result};
25use crate::for_encoding::{ForDecoder, ForEncoder};
26use crate::gorilla::{GorillaDecoder, GorillaEncoder};
27use crate::integer::{IntegerDecoder, IntegerEncoder, VarintDecoder, VarintEncoder};
28use crate::patas::PatasDecoder;
29use crate::plain::{PlainDecoder, PlainEncoder};
30use crate::rle::{RleDecoder, RleEncoder};
31
32macro_rules! encoding_types {
48 ($(
49 $(#[$meta:meta])*
50 $variant:ident = $tag:literal, $name:literal;
51 )+) => {
52 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
54 #[repr(u8)]
55 pub enum EncodingType {
56 $(
57 $(#[$meta])*
58 $variant = $tag,
59 )+
60 }
61
62 impl EncodingType {
63 pub const ALL: &'static [Self] = &[$(Self::$variant),+];
69
70 pub fn from_tag(tag: u8) -> Result<Self> {
76 match tag {
77 $($tag => Ok(Self::$variant),)+
78 other => Err(EncodingError::UnsupportedEncoding { tag: other }),
79 }
80 }
81
82 #[must_use]
84 pub fn tag(self) -> u8 {
85 self as u8
86 }
87 }
88
89 impl std::fmt::Display for EncodingType {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 f.write_str(match self {
92 $(Self::$variant => $name,)+
93 })
94 }
95 }
96 };
97}
98
99encoding_types! {
100 DeltaOfDelta = 0, "delta-of-delta";
105 Chimp = 1, "chimp";
107 Gorilla = 2, "gorilla";
109 IntegerI64 = 3, "integer-i64";
111 IntegerU64 = 4, "integer-u64";
113 Dictionary = 5, "dictionary";
115 Bitmap = 6, "bitmap";
117 PlainF64 = 7, "plain-f64";
119 PlainI64 = 8, "plain-i64";
121 PlainU64 = 9, "plain-u64";
123 PlainBool = 10, "plain-bool";
125 PlainString = 11, "plain-string";
127 Nullable = 12, "nullable";
136 Rle = 13, "rle";
138 VarintI64 = 14, "varint-i64";
144 VarintU64 = 15, "varint-u64";
146 Chimp128 = 16, "chimp128";
148 ForI64 = 17, "for-i64";
150 ForU64 = 18, "for-u64";
152 Patas = 19, "patas";
154 Alp = 20, "alp";
160 Pco = 21, "pco";
163 PcoI64 = 22, "pco_i64";
165 PcoU64 = 23, "pco_u64";
167}
168
169#[derive(Debug, Clone)]
177pub struct EncodedBlock {
178 pub encoding: EncodingType,
180 pub payload: Vec<u8>,
182}
183
184impl EncodedBlock {
185 #[must_use]
187 pub fn to_bytes(&self) -> Vec<u8> {
188 let mut out = Vec::with_capacity(1 + self.payload.len());
189 out.push(self.encoding.tag());
190 out.extend_from_slice(&self.payload);
191 out
192 }
193
194 pub fn from_bytes(data: &[u8]) -> Result<Self> {
200 if data.is_empty() {
201 return Err(EncodingError::CorruptData {
202 detail: "empty encoded block".to_string(),
203 });
204 }
205 let encoding = EncodingType::from_tag(data[0])?;
206 let payload = data[1..].to_vec();
207 Ok(Self { encoding, payload })
208 }
209}
210
211const MIN_COMPRESSION_RATIO: f64 = 1.5;
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
232pub enum TimestampEncoding {
233 #[default]
235 Auto,
236 Pco,
238 DeltaOfDelta,
240 Plain,
242}
243
244#[derive(Debug, Clone, Copy)]
260pub struct ColumnEncoder;
261
262impl ColumnEncoder {
263 pub fn encode_timestamps(values: &[i64]) -> Result<EncodedBlock> {
273 Self::encode_timestamps_with(values, TimestampEncoding::Auto)
274 }
275
276 pub fn encode_timestamps_with(
311 values: &[i64],
312 strategy: TimestampEncoding,
313 ) -> Result<EncodedBlock> {
314 let raw_size = values.len() * 8;
315
316 let (encoding, payload) = match strategy {
317 TimestampEncoding::Plain => {
318 return Ok(EncodedBlock {
319 encoding: EncodingType::PlainI64,
320 payload: PlainEncoder::encode_i64(values)?,
321 })
322 }
323 TimestampEncoding::DeltaOfDelta => (
324 EncodingType::DeltaOfDelta,
325 DeltaOfDeltaEncoder::encode(values)?,
326 ),
327 TimestampEncoding::Pco => (
328 EncodingType::PcoI64,
329 crate::pco::PcoEncoder::encode_timestamps(values)?,
330 ),
331 TimestampEncoding::Auto => {
332 let pco = crate::pco::PcoEncoder::encode_timestamps(values)?;
333 let dod = DeltaOfDeltaEncoder::encode(values)?;
334 if dod.len() < pco.len() {
337 (EncodingType::DeltaOfDelta, dod)
338 } else {
339 (EncodingType::PcoI64, pco)
340 }
341 }
342 };
343
344 if should_use_specialized(&payload, raw_size) {
345 Ok(EncodedBlock { encoding, payload })
346 } else {
347 Ok(EncodedBlock {
348 encoding: EncodingType::PlainI64,
349 payload: PlainEncoder::encode_i64(values)?,
350 })
351 }
352 }
353
354 pub fn encode_f64(values: &[f64], strategy: FloatEncoding) -> Result<EncodedBlock> {
364 match strategy {
365 FloatEncoding::Chimp => {
366 let selector = crate::adaptive::AdaptiveSelector::new();
370 selector.encode_f64_adaptive(values)
371 }
372 FloatEncoding::Gorilla => {
373 let raw_size = values.len() * 8;
374 let gorilla_payload = GorillaEncoder::encode(values)?;
375 if should_use_specialized(&gorilla_payload, raw_size) {
376 return Ok(EncodedBlock {
377 encoding: EncodingType::Gorilla,
378 payload: gorilla_payload,
379 });
380 }
381 Ok(EncodedBlock {
382 encoding: EncodingType::PlainF64,
383 payload: PlainEncoder::encode_f64(values)?,
384 })
385 }
386 FloatEncoding::Plain => Ok(EncodedBlock {
387 encoding: EncodingType::PlainF64,
388 payload: PlainEncoder::encode_f64(values)?,
389 }),
390 }
391 }
392
393 pub fn encode_i64(values: &[i64]) -> Result<EncodedBlock> {
403 let selector = crate::adaptive::AdaptiveSelector::new();
404 let pattern = selector.analyze_integers(values);
405
406 if pattern == crate::adaptive::IntegerPattern::Constant {
408 let raw_size = values.len() * 8;
409 let rle_payload = RleEncoder::encode_i64(values)?;
410 if should_use_specialized(&rle_payload, raw_size) {
411 return Ok(EncodedBlock {
412 encoding: EncodingType::Rle,
413 payload: rle_payload,
414 });
415 }
416 }
417
418 let raw_size = values.len() * 8;
419
420 let fixed_payload = IntegerEncoder::encode_i64(values)?;
422 let varint_payload = VarintEncoder::encode_i64(values)?;
423 let for_payload = ForEncoder::encode_i64(values)?;
424 let pco_payload = crate::pco::PcoEncoder::encode_i64(values)?;
425
426 let mut best_enc = EncodingType::IntegerI64;
427 let mut best_payload = fixed_payload;
428
429 if varint_payload.len() < best_payload.len() {
430 best_enc = EncodingType::VarintI64;
431 best_payload = varint_payload;
432 }
433 if for_payload.len() < best_payload.len() {
434 best_enc = EncodingType::ForI64;
435 best_payload = for_payload;
436 }
437 if pco_payload.len() < best_payload.len() {
438 best_enc = EncodingType::PcoI64;
439 best_payload = pco_payload;
440 }
441
442 if should_use_specialized(&best_payload, raw_size) {
443 Ok(EncodedBlock {
444 encoding: best_enc,
445 payload: best_payload,
446 })
447 } else {
448 Ok(EncodedBlock {
449 encoding: EncodingType::PlainI64,
450 payload: PlainEncoder::encode_i64(values)?,
451 })
452 }
453 }
454
455 pub fn encode_u64(values: &[u64]) -> Result<EncodedBlock> {
465 let as_i64: Vec<i64> = values.iter().map(|&v| v as i64).collect();
467 let selector = crate::adaptive::AdaptiveSelector::new();
468 if selector.analyze_integers(&as_i64) == crate::adaptive::IntegerPattern::Constant {
469 let raw_size = values.len() * 8;
470 let rle_payload = RleEncoder::encode_u64(values)?;
471 if should_use_specialized(&rle_payload, raw_size) {
472 return Ok(EncodedBlock {
473 encoding: EncodingType::Rle,
474 payload: rle_payload,
475 });
476 }
477 }
478
479 let raw_size = values.len() * 8;
480 let fixed_payload = IntegerEncoder::encode_u64(values)?;
481 let varint_payload = VarintEncoder::encode_u64(values)?;
482 let for_payload = ForEncoder::encode_u64(values)?;
483 let pco_payload = crate::pco::PcoEncoder::encode_u64(values)?;
484
485 let mut best_enc = EncodingType::IntegerU64;
487 let mut best_payload = fixed_payload;
488
489 if varint_payload.len() < best_payload.len() {
490 best_enc = EncodingType::VarintU64;
491 best_payload = varint_payload;
492 }
493 if for_payload.len() < best_payload.len() {
494 best_enc = EncodingType::ForU64;
495 best_payload = for_payload;
496 }
497 if pco_payload.len() < best_payload.len() {
498 best_enc = EncodingType::PcoU64;
499 best_payload = pco_payload;
500 }
501
502 if should_use_specialized(&best_payload, raw_size) {
503 Ok(EncodedBlock {
504 encoding: best_enc,
505 payload: best_payload,
506 })
507 } else {
508 Ok(EncodedBlock {
509 encoding: EncodingType::PlainU64,
510 payload: PlainEncoder::encode_u64(values)?,
511 })
512 }
513 }
514
515 pub fn encode_string(values: &[&str]) -> Result<EncodedBlock> {
524 let raw_size: usize = values.iter().map(|s| 4 + s.len()).sum();
525
526 match DictionaryEncoder::encode(values) {
527 Ok(payload) if should_use_specialized(&payload, raw_size) => Ok(EncodedBlock {
528 encoding: EncodingType::Dictionary,
529 payload,
530 }),
531 Ok(_) | Err(EncodingError::DictionaryOverflow { .. }) => Ok(EncodedBlock {
533 encoding: EncodingType::PlainString,
534 payload: PlainEncoder::encode_string(values)?,
535 }),
536 Err(e) => Err(e),
538 }
539 }
540
541 pub fn encode_bool(values: &[bool]) -> Result<EncodedBlock> {
549 if !values.is_empty() {
551 let first = values[0];
552 if values.iter().all(|&v| v == first) {
553 let raw_size = values.len(); let rle_payload = RleEncoder::encode_bool(values)?;
555 if should_use_specialized(&rle_payload, raw_size) {
556 return Ok(EncodedBlock {
557 encoding: EncodingType::Rle,
558 payload: rle_payload,
559 });
560 }
561 }
562 }
563 Ok(EncodedBlock {
564 encoding: EncodingType::Bitmap,
565 payload: BitmapEncoder::encode(values)?,
566 })
567 }
568
569 pub fn encode_f64_nullable(
581 values: &[Option<f64>],
582 strategy: FloatEncoding,
583 ) -> Result<EncodedBlock> {
584 if values.is_empty() {
585 return Err(EncodingError::EmptyInput {
586 context: "nullable f64 encoder",
587 });
588 }
589 let (non_null, validity) = extract_non_null_f64(values);
590 if non_null.is_empty() {
591 return Ok(wrap_nullable(
593 EncodingType::PlainF64,
594 values.len(),
595 &validity,
596 &[],
597 ));
598 }
599 let inner = Self::encode_f64(&non_null, strategy)?;
600 Ok(wrap_nullable(
601 inner.encoding,
602 values.len(),
603 &validity,
604 &inner.payload,
605 ))
606 }
607
608 pub fn encode_i64_nullable(values: &[Option<i64>]) -> Result<EncodedBlock> {
614 if values.is_empty() {
615 return Err(EncodingError::EmptyInput {
616 context: "nullable i64 encoder",
617 });
618 }
619 let (non_null, validity) = extract_non_null_i64(values);
620 if non_null.is_empty() {
621 return Ok(wrap_nullable(
622 EncodingType::PlainI64,
623 values.len(),
624 &validity,
625 &[],
626 ));
627 }
628 let inner = Self::encode_i64(&non_null)?;
629 Ok(wrap_nullable(
630 inner.encoding,
631 values.len(),
632 &validity,
633 &inner.payload,
634 ))
635 }
636
637 pub fn encode_u64_nullable(values: &[Option<u64>]) -> Result<EncodedBlock> {
643 if values.is_empty() {
644 return Err(EncodingError::EmptyInput {
645 context: "nullable u64 encoder",
646 });
647 }
648 let (non_null, validity) = extract_non_null_u64(values);
649 if non_null.is_empty() {
650 return Ok(wrap_nullable(
651 EncodingType::PlainU64,
652 values.len(),
653 &validity,
654 &[],
655 ));
656 }
657 let inner = Self::encode_u64(&non_null)?;
658 Ok(wrap_nullable(
659 inner.encoding,
660 values.len(),
661 &validity,
662 &inner.payload,
663 ))
664 }
665
666 pub fn encode_string_nullable(values: &[Option<&str>]) -> Result<EncodedBlock> {
672 if values.is_empty() {
673 return Err(EncodingError::EmptyInput {
674 context: "nullable string encoder",
675 });
676 }
677 let (non_null, validity): (Vec<&str>, Vec<bool>) = values
678 .iter()
679 .map(|v| match v {
680 Some(s) => (*s, true),
681 None => ("", false),
682 })
683 .unzip();
684 let non_null_only: Vec<&str> = non_null
685 .into_iter()
686 .zip(validity.iter())
687 .filter(|(_, v)| **v)
688 .map(|(s, _)| s)
689 .collect();
690 if non_null_only.is_empty() {
691 return Ok(wrap_nullable(
692 EncodingType::PlainString,
693 values.len(),
694 &validity,
695 &[],
696 ));
697 }
698 let inner = Self::encode_string(&non_null_only)?;
699 Ok(wrap_nullable(
700 inner.encoding,
701 values.len(),
702 &validity,
703 &inner.payload,
704 ))
705 }
706
707 pub fn encode_bool_nullable(values: &[Option<bool>]) -> Result<EncodedBlock> {
713 if values.is_empty() {
714 return Err(EncodingError::EmptyInput {
715 context: "nullable bool encoder",
716 });
717 }
718 let validity: Vec<bool> = values.iter().map(std::option::Option::is_some).collect();
719 let non_null: Vec<bool> = values.iter().filter_map(|v| *v).collect();
720 if non_null.is_empty() {
721 return Ok(wrap_nullable(
722 EncodingType::Bitmap,
723 values.len(),
724 &validity,
725 &[],
726 ));
727 }
728 let inner = Self::encode_bool(&non_null)?;
729 Ok(wrap_nullable(
730 inner.encoding,
731 values.len(),
732 &validity,
733 &inner.payload,
734 ))
735 }
736}
737
738#[derive(Debug, Clone)]
750pub enum DecodedColumn {
751 I64(Vec<i64>),
753 U64(Vec<u64>),
755 F64(Vec<f64>),
757 Bool(Vec<bool>),
759 String(Vec<String>),
761 NullableI64(Vec<Option<i64>>),
763 NullableU64(Vec<Option<u64>>),
765 NullableF64(Vec<Option<f64>>),
767 NullableBool(Vec<Option<bool>>),
769 NullableString(Vec<Option<String>>),
771}
772
773fn f64_slice_eq(a: &[f64], b: &[f64]) -> bool {
775 a.len() == b.len() && a.iter().zip(b).all(|(x, y)| x.to_bits() == y.to_bits())
776}
777
778fn opt_f64_slice_eq(a: &[Option<f64>], b: &[Option<f64>]) -> bool {
780 a.len() == b.len()
781 && a.iter().zip(b).all(|(x, y)| match (x, y) {
782 (Some(x), Some(y)) => x.to_bits() == y.to_bits(),
783 (None, None) => true,
784 _ => false,
785 })
786}
787
788impl PartialEq for DecodedColumn {
789 fn eq(&self, other: &Self) -> bool {
790 match (self, other) {
791 (Self::I64(a), Self::I64(b)) => a == b,
792 (Self::U64(a), Self::U64(b)) => a == b,
793 (Self::F64(a), Self::F64(b)) => f64_slice_eq(a, b),
794 (Self::Bool(a), Self::Bool(b)) => a == b,
795 (Self::String(a), Self::String(b)) => a == b,
796 (Self::NullableI64(a), Self::NullableI64(b)) => a == b,
797 (Self::NullableU64(a), Self::NullableU64(b)) => a == b,
798 (Self::NullableF64(a), Self::NullableF64(b)) => opt_f64_slice_eq(a, b),
799 (Self::NullableBool(a), Self::NullableBool(b)) => a == b,
800 (Self::NullableString(a), Self::NullableString(b)) => a == b,
801 _ => false,
802 }
803 }
804}
805
806impl DecodedColumn {
807 #[must_use]
809 pub fn len(&self) -> usize {
810 match self {
811 Self::I64(v) => v.len(),
812 Self::U64(v) => v.len(),
813 Self::F64(v) => v.len(),
814 Self::Bool(v) => v.len(),
815 Self::String(v) => v.len(),
816 Self::NullableI64(v) => v.len(),
817 Self::NullableU64(v) => v.len(),
818 Self::NullableF64(v) => v.len(),
819 Self::NullableBool(v) => v.len(),
820 Self::NullableString(v) => v.len(),
821 }
822 }
823
824 #[must_use]
826 pub fn is_empty(&self) -> bool {
827 self.len() == 0
828 }
829}
830
831#[derive(Debug, Clone, Copy)]
839pub struct ColumnDecoder;
840
841impl ColumnDecoder {
842 pub fn decode(block: &EncodedBlock) -> Result<DecodedColumn> {
849 match block.encoding {
850 EncodingType::DeltaOfDelta => {
851 let values = DeltaOfDeltaDecoder::decode(&block.payload)?;
852 Ok(DecodedColumn::I64(values))
853 }
854 EncodingType::Chimp => {
855 let values = ChimpDecoder::decode(&block.payload)?;
856 Ok(DecodedColumn::F64(values))
857 }
858 EncodingType::Chimp128 => {
859 let values = Chimp128Decoder::decode(&block.payload)?;
860 Ok(DecodedColumn::F64(values))
861 }
862 EncodingType::Patas => {
863 let values = PatasDecoder::decode(&block.payload)?;
864 Ok(DecodedColumn::F64(values))
865 }
866 EncodingType::Alp => {
867 let values = AlpDecoder::decode(&block.payload)?;
868 Ok(DecodedColumn::F64(values))
869 }
870 EncodingType::Pco => {
871 let values = crate::pco::PcoDecoder::decode_f64(&block.payload)?;
872 Ok(DecodedColumn::F64(values))
873 }
874 EncodingType::PcoI64 => {
875 let values = crate::pco::PcoDecoder::decode_i64(&block.payload)?;
876 Ok(DecodedColumn::I64(values))
877 }
878 EncodingType::PcoU64 => {
879 let values = crate::pco::PcoDecoder::decode_u64(&block.payload)?;
880 Ok(DecodedColumn::U64(values))
881 }
882 EncodingType::Gorilla => {
883 let values = GorillaDecoder::decode(&block.payload)?;
884 Ok(DecodedColumn::F64(values))
885 }
886 EncodingType::IntegerI64 => {
887 let values = IntegerDecoder::decode_i64(&block.payload)?;
888 Ok(DecodedColumn::I64(values))
889 }
890 EncodingType::IntegerU64 => {
891 let values = IntegerDecoder::decode_u64(&block.payload)?;
892 Ok(DecodedColumn::U64(values))
893 }
894 EncodingType::Dictionary => {
895 let values = DictionaryDecoder::decode(&block.payload)?;
896 Ok(DecodedColumn::String(values))
897 }
898 EncodingType::Bitmap => {
899 let values = BitmapDecoder::decode(&block.payload)?;
900 Ok(DecodedColumn::Bool(values))
901 }
902 EncodingType::PlainF64 => {
903 let values = PlainDecoder::decode_f64(&block.payload)?;
904 Ok(DecodedColumn::F64(values))
905 }
906 EncodingType::PlainI64 => {
907 let values = PlainDecoder::decode_i64(&block.payload)?;
908 Ok(DecodedColumn::I64(values))
909 }
910 EncodingType::PlainU64 => {
911 let values = PlainDecoder::decode_u64(&block.payload)?;
912 Ok(DecodedColumn::U64(values))
913 }
914 EncodingType::PlainBool => {
915 let values = PlainDecoder::decode_bool(&block.payload)?;
916 Ok(DecodedColumn::Bool(values))
917 }
918 EncodingType::PlainString => {
919 let values = PlainDecoder::decode_string(&block.payload)?;
920 Ok(DecodedColumn::String(values))
921 }
922 EncodingType::Nullable => Self::decode_nullable(&block.payload),
923 EncodingType::Rle => Self::decode_rle(&block.payload),
924 EncodingType::VarintI64 => {
925 let values = VarintDecoder::decode_i64(&block.payload)?;
926 Ok(DecodedColumn::I64(values))
927 }
928 EncodingType::VarintU64 => {
929 let values = VarintDecoder::decode_u64(&block.payload)?;
930 Ok(DecodedColumn::U64(values))
931 }
932 EncodingType::ForI64 => {
933 let values = ForDecoder::decode_i64(&block.payload)?;
934 Ok(DecodedColumn::I64(values))
935 }
936 EncodingType::ForU64 => {
937 let values = ForDecoder::decode_u64(&block.payload)?;
938 Ok(DecodedColumn::U64(values))
939 }
940 }
941 }
942
943 fn decode_nullable(payload: &[u8]) -> Result<DecodedColumn> {
951 if payload.len() < 5 {
952 return Err(EncodingError::CorruptData {
953 detail: "nullable payload too short".to_string(),
954 });
955 }
956
957 let inner_enc = EncodingType::from_tag(payload[0])?;
958 let total_count = crate::coding::checked_decode_count(
962 u32::from_le_bytes([payload[1], payload[2], payload[3], payload[4]]) as usize,
963 "nullable",
964 )?;
965 let bitmap_bytes = total_count.div_ceil(8);
966 let header_len = 5 + bitmap_bytes;
967
968 if payload.len() < header_len {
969 return Err(EncodingError::CorruptData {
970 detail: "nullable bitmap truncated".to_string(),
971 });
972 }
973
974 let bitmap = &payload[5..header_len];
975 let inner_payload = &payload[header_len..];
976
977 let validity: Vec<bool> = (0..total_count)
979 .map(|i| bitmap[i / 8] & (1 << (i % 8)) != 0)
980 .collect();
981
982 let non_null_count = validity.iter().filter(|v| **v).count();
983
984 if non_null_count == 0 {
986 return match inner_enc {
987 EncodingType::PlainF64
988 | EncodingType::Chimp
989 | EncodingType::Chimp128
990 | EncodingType::Gorilla
991 | EncodingType::Patas
992 | EncodingType::Alp
993 | EncodingType::Pco => Ok(DecodedColumn::NullableF64(vec![None; total_count])),
994 EncodingType::PlainI64
995 | EncodingType::IntegerI64
996 | EncodingType::VarintI64
997 | EncodingType::ForI64
998 | EncodingType::PcoI64
999 | EncodingType::DeltaOfDelta => {
1000 Ok(DecodedColumn::NullableI64(vec![None; total_count]))
1001 }
1002 EncodingType::PlainU64
1003 | EncodingType::IntegerU64
1004 | EncodingType::VarintU64
1005 | EncodingType::ForU64
1006 | EncodingType::PcoU64 => Ok(DecodedColumn::NullableU64(vec![None; total_count])),
1007 EncodingType::PlainString | EncodingType::Dictionary => {
1008 Ok(DecodedColumn::NullableString(vec![None; total_count]))
1009 }
1010 EncodingType::Bitmap | EncodingType::PlainBool => {
1011 Ok(DecodedColumn::NullableBool(vec![None; total_count]))
1012 }
1013 _ => Err(EncodingError::CorruptData {
1014 detail: format!("unexpected inner encoding in nullable: {inner_enc}"),
1015 }),
1016 };
1017 }
1018
1019 let inner_block = EncodedBlock {
1021 encoding: inner_enc,
1022 payload: inner_payload.to_vec(),
1023 };
1024 let decoded = Self::decode(&inner_block)?;
1025
1026 match decoded {
1028 DecodedColumn::F64(vals) => Ok(DecodedColumn::NullableF64(scatter(vals, &validity))),
1029 DecodedColumn::I64(vals) => Ok(DecodedColumn::NullableI64(scatter(vals, &validity))),
1030 DecodedColumn::U64(vals) => Ok(DecodedColumn::NullableU64(scatter(vals, &validity))),
1031 DecodedColumn::Bool(vals) => Ok(DecodedColumn::NullableBool(scatter(vals, &validity))),
1032 DecodedColumn::String(vals) => {
1033 Ok(DecodedColumn::NullableString(scatter(vals, &validity)))
1034 }
1035 _ => Err(EncodingError::CorruptData {
1036 detail: "unexpected decoded type in nullable wrapper".to_string(),
1037 }),
1038 }
1039 }
1040
1041 fn decode_rle(payload: &[u8]) -> Result<DecodedColumn> {
1043 let vtype = RleDecoder::value_type(payload)?;
1044 match vtype {
1045 0 => Ok(DecodedColumn::I64(RleDecoder::decode_i64(payload)?)),
1046 1 => Ok(DecodedColumn::U64(RleDecoder::decode_u64(payload)?)),
1047 2 => Ok(DecodedColumn::F64(RleDecoder::decode_f64(payload)?)),
1048 3 => Ok(DecodedColumn::Bool(RleDecoder::decode_bool(payload)?)),
1049 4 => Ok(DecodedColumn::String(RleDecoder::decode_string(payload)?)),
1050 other => Err(EncodingError::CorruptData {
1051 detail: format!("unknown RLE value type: {other}"),
1052 }),
1053 }
1054 }
1055
1056 pub fn decode_bytes(data: &[u8]) -> Result<DecodedColumn> {
1063 let block = EncodedBlock::from_bytes(data)?;
1064 Self::decode(&block)
1065 }
1066}
1067
1068pub(crate) fn should_use_specialized(encoded: &[u8], raw_size: usize) -> bool {
1075 if raw_size == 0 {
1076 return false;
1077 }
1078 #[allow(clippy::cast_precision_loss)]
1079 let ratio = raw_size as f64 / encoded.len() as f64;
1080 ratio >= MIN_COMPRESSION_RATIO
1081}
1082
1083fn encode_validity_bitmap(validity: &[bool]) -> Vec<u8> {
1091 let byte_count = validity.len().div_ceil(8);
1092 let mut bitmap = vec![0u8; byte_count];
1093 for (i, &valid) in validity.iter().enumerate() {
1094 if valid {
1095 bitmap[i / 8] |= 1 << (i % 8);
1096 }
1097 }
1098 bitmap
1099}
1100
1101fn wrap_nullable(
1103 inner_enc: EncodingType,
1104 total_count: usize,
1105 validity: &[bool],
1106 inner_payload: &[u8],
1107) -> EncodedBlock {
1108 let bitmap = encode_validity_bitmap(validity);
1109 let mut payload = Vec::with_capacity(1 + 4 + bitmap.len() + inner_payload.len());
1110 payload.push(inner_enc.tag());
1111 payload.extend_from_slice(&(total_count as u32).to_le_bytes());
1112 payload.extend_from_slice(&bitmap);
1113 payload.extend_from_slice(inner_payload);
1114 EncodedBlock {
1115 encoding: EncodingType::Nullable,
1116 payload,
1117 }
1118}
1119
1120fn extract_non_null_f64(values: &[Option<f64>]) -> (Vec<f64>, Vec<bool>) {
1122 let validity: Vec<bool> = values.iter().map(std::option::Option::is_some).collect();
1123 let non_null: Vec<f64> = values.iter().filter_map(|v| *v).collect();
1124 (non_null, validity)
1125}
1126
1127fn extract_non_null_i64(values: &[Option<i64>]) -> (Vec<i64>, Vec<bool>) {
1129 let validity: Vec<bool> = values.iter().map(std::option::Option::is_some).collect();
1130 let non_null: Vec<i64> = values.iter().filter_map(|v| *v).collect();
1131 (non_null, validity)
1132}
1133
1134fn extract_non_null_u64(values: &[Option<u64>]) -> (Vec<u64>, Vec<bool>) {
1136 let validity: Vec<bool> = values.iter().map(std::option::Option::is_some).collect();
1137 let non_null: Vec<u64> = values.iter().filter_map(|v| *v).collect();
1138 (non_null, validity)
1139}
1140
1141fn scatter<T: Default + Clone>(values: Vec<T>, validity: &[bool]) -> Vec<Option<T>> {
1146 let mut result = Vec::with_capacity(validity.len());
1147 let mut val_iter = values.into_iter();
1148 for &valid in validity {
1149 if valid {
1150 result.push(val_iter.next().map(Some).unwrap_or(None));
1151 } else {
1152 result.push(None);
1153 }
1154 }
1155 result
1156}
1157
1158#[cfg(test)]
1163mod tests {
1164 use super::*;
1165
1166 #[test]
1169 fn encoding_type_tag_roundtrip() {
1170 let all = [
1171 EncodingType::DeltaOfDelta,
1172 EncodingType::Chimp,
1173 EncodingType::Gorilla,
1174 EncodingType::IntegerI64,
1175 EncodingType::IntegerU64,
1176 EncodingType::Dictionary,
1177 EncodingType::Bitmap,
1178 EncodingType::PlainF64,
1179 EncodingType::PlainI64,
1180 EncodingType::PlainU64,
1181 EncodingType::PlainBool,
1182 EncodingType::PlainString,
1183 EncodingType::Nullable,
1184 EncodingType::Rle,
1185 ];
1186 for enc in &all {
1187 let tag = enc.tag();
1188 let recovered = EncodingType::from_tag(tag).unwrap();
1189 assert_eq!(*enc, recovered);
1190 }
1191 }
1192
1193 #[test]
1194 fn encoding_type_unknown_tag() {
1195 assert!(EncodingType::from_tag(255).is_err());
1196 }
1197
1198 #[test]
1203 fn all_lists_every_variant_and_agrees_with_from_tag() {
1204 let scanned: Vec<EncodingType> = (0..=u8::MAX)
1205 .filter_map(|t| EncodingType::from_tag(t).ok())
1206 .collect();
1207 assert_eq!(EncodingType::ALL, scanned.as_slice());
1208 for enc in EncodingType::ALL {
1209 assert_eq!(EncodingType::from_tag(enc.tag()).unwrap(), *enc);
1210 }
1211 for (i, enc) in EncodingType::ALL.iter().enumerate() {
1214 assert_eq!(usize::from(enc.tag()), i, "{enc} has a non-dense tag");
1215 }
1216 }
1217
1218 #[test]
1221 fn encoded_block_roundtrip() {
1222 let block = EncodedBlock {
1223 encoding: EncodingType::Chimp,
1224 payload: vec![1, 2, 3, 4],
1225 };
1226 let bytes = block.to_bytes();
1227 assert_eq!(bytes[0], EncodingType::Chimp.tag());
1228 let recovered = EncodedBlock::from_bytes(&bytes).unwrap();
1229 assert_eq!(recovered.encoding, block.encoding);
1230 assert_eq!(recovered.payload, block.payload);
1231 }
1232
1233 #[test]
1234 fn encoded_block_empty_error() {
1235 assert!(EncodedBlock::from_bytes(&[]).is_err());
1236 }
1237
1238 #[test]
1241 fn encode_timestamps_monotonic() {
1242 let ts: Vec<i64> = (0..1000).map(|i| 1_000_000 + i * 10_000).collect();
1243 let block = ColumnEncoder::encode_timestamps(&ts).unwrap();
1244 assert_eq!(block.encoding, EncodingType::PcoI64);
1248 let decoded = ColumnDecoder::decode(&block).unwrap();
1249 assert_eq!(decoded, DecodedColumn::I64(ts));
1250 }
1251
1252 #[test]
1253 fn encode_timestamps_honours_an_explicit_codec() {
1254 let ts: Vec<i64> = (0..1000).map(|i| 1_000_000 + i * 10_000).collect();
1255 for (strategy, expected) in [
1256 (TimestampEncoding::Pco, EncodingType::PcoI64),
1257 (TimestampEncoding::DeltaOfDelta, EncodingType::DeltaOfDelta),
1258 (TimestampEncoding::Plain, EncodingType::PlainI64),
1259 (TimestampEncoding::Auto, EncodingType::PcoI64),
1260 ] {
1261 let block = ColumnEncoder::encode_timestamps_with(&ts, strategy).unwrap();
1262 assert_eq!(block.encoding, expected, "{strategy:?}");
1263 assert_eq!(
1264 ColumnDecoder::decode(&block).unwrap(),
1265 DecodedColumn::I64(ts.clone())
1266 );
1267 }
1268 }
1269
1270 #[test]
1272 fn auto_timestamps_never_loses_to_delta_of_delta() {
1273 let mut seed = 7u64;
1276 let ts: Vec<i64> = (0..2000)
1277 .map(|_| {
1278 seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
1279 (seed >> 20) as i64
1280 })
1281 .collect();
1282 let auto = ColumnEncoder::encode_timestamps_with(&ts, TimestampEncoding::Auto).unwrap();
1283 let dod =
1284 ColumnEncoder::encode_timestamps_with(&ts, TimestampEncoding::DeltaOfDelta).unwrap();
1285 let pco = ColumnEncoder::encode_timestamps_with(&ts, TimestampEncoding::Pco).unwrap();
1286 assert!(auto.payload.len() <= dod.payload.len().min(pco.payload.len()));
1287 assert_eq!(
1288 ColumnDecoder::decode(&auto).unwrap(),
1289 DecodedColumn::I64(ts)
1290 );
1291 }
1292
1293 #[test]
1294 fn encode_timestamps_bytes_roundtrip() {
1295 let ts: Vec<i64> = (0..100).map(|i| 1_000_000 + i * 10_000).collect();
1296 let block = ColumnEncoder::encode_timestamps(&ts).unwrap();
1297 let raw = block.to_bytes();
1298 let decoded = ColumnDecoder::decode_bytes(&raw).unwrap();
1299 assert_eq!(decoded, DecodedColumn::I64(ts));
1300 }
1301
1302 #[test]
1305 fn encode_f64_adaptive_strategy() {
1306 let values: Vec<f64> = (0..500).map(|i| 20.0 + (i as f64) * 0.01).collect();
1309 let block = ColumnEncoder::encode_f64(&values, FloatEncoding::Chimp).unwrap();
1310 assert_eq!(block.encoding, EncodingType::Pco);
1311 let decoded = ColumnDecoder::decode(&block).unwrap();
1312 assert_eq!(decoded, DecodedColumn::F64(values));
1313 }
1314
1315 #[test]
1316 fn encode_f64_gorilla_strategy() {
1317 let values: Vec<f64> = (0..500).map(|i| 20.0 + (i as f64) * 0.01).collect();
1318 let block = ColumnEncoder::encode_f64(&values, FloatEncoding::Gorilla).unwrap();
1319 assert!(
1320 block.encoding == EncodingType::Gorilla || block.encoding == EncodingType::PlainF64
1321 );
1322 let decoded = ColumnDecoder::decode(&block).unwrap();
1323 assert_eq!(decoded, DecodedColumn::F64(values));
1324 }
1325
1326 #[test]
1327 fn encode_f64_plain_strategy() {
1328 let values = vec![1.0, 2.0, 3.0];
1329 let block = ColumnEncoder::encode_f64(&values, FloatEncoding::Plain).unwrap();
1330 assert_eq!(block.encoding, EncodingType::PlainF64);
1331 let decoded = ColumnDecoder::decode(&block).unwrap();
1332 assert_eq!(decoded, DecodedColumn::F64(values));
1333 }
1334
1335 #[test]
1338 fn encode_i64_with_fallback() {
1339 let values: Vec<i64> = (0..1000).collect();
1340 let block = ColumnEncoder::encode_i64(&values).unwrap();
1341 assert!(
1342 block.encoding == EncodingType::PcoI64
1343 || block.encoding == EncodingType::IntegerI64
1344 || block.encoding == EncodingType::PlainI64
1345 );
1346 let decoded = ColumnDecoder::decode(&block).unwrap();
1347 assert_eq!(decoded, DecodedColumn::I64(values));
1348 }
1349
1350 #[test]
1351 fn encode_u64_with_fallback() {
1352 let values: Vec<u64> = (0..1000).collect();
1353 let block = ColumnEncoder::encode_u64(&values).unwrap();
1354 assert!(
1355 block.encoding == EncodingType::PcoU64
1356 || block.encoding == EncodingType::IntegerU64
1357 || block.encoding == EncodingType::PlainU64
1358 );
1359 let decoded = ColumnDecoder::decode(&block).unwrap();
1360 assert_eq!(decoded, DecodedColumn::U64(values));
1361 }
1362
1363 #[test]
1366 fn encode_string_low_cardinality() {
1367 let raw = vec!["us-east-1"; 500];
1368 let block = ColumnEncoder::encode_string(&raw).unwrap();
1369 assert_eq!(block.encoding, EncodingType::Dictionary);
1370 let decoded = ColumnDecoder::decode(&block).unwrap();
1371 let expected: Vec<String> = raw.iter().map(|s| (*s).to_owned()).collect();
1372 assert_eq!(decoded, DecodedColumn::String(expected));
1373 }
1374
1375 #[test]
1376 fn encode_string_high_cardinality_fallback() {
1377 let raw: Vec<String> = (0..200).map(|i| format!("uuid-{i}")).collect();
1379 let refs: Vec<&str> = raw.iter().map(String::as_str).collect();
1380 let block = ColumnEncoder::encode_string(&refs).unwrap();
1381 let decoded = ColumnDecoder::decode(&block).unwrap();
1382 assert_eq!(decoded, DecodedColumn::String(raw));
1383 }
1384
1385 #[test]
1388 fn encode_bool_bitmap() {
1389 let values: Vec<bool> = (0..100).map(|i| i % 3 == 0).collect();
1390 let block = ColumnEncoder::encode_bool(&values).unwrap();
1391 assert_eq!(block.encoding, EncodingType::Bitmap);
1392 let decoded = ColumnDecoder::decode(&block).unwrap();
1393 assert_eq!(decoded, DecodedColumn::Bool(values));
1394 }
1395
1396 #[test]
1399 fn empty_inputs_error() {
1400 assert!(ColumnEncoder::encode_timestamps(&[]).is_err());
1401 assert!(ColumnEncoder::encode_f64(&[], FloatEncoding::Chimp).is_err());
1402 assert!(ColumnEncoder::encode_i64(&[]).is_err());
1403 assert!(ColumnEncoder::encode_u64(&[]).is_err());
1404 let empty: &[&str] = &[];
1405 assert!(ColumnEncoder::encode_string(empty).is_err());
1406 assert!(ColumnEncoder::encode_bool(&[]).is_err());
1407 }
1408
1409 #[test]
1412 fn should_use_specialized_logic() {
1413 assert!(should_use_specialized(&[0u8; 50], 100));
1415 assert!(!should_use_specialized(&[0u8; 80], 100));
1417 assert!(!should_use_specialized(&[0u8; 10], 0));
1419 }
1420
1421 #[test]
1424 fn nullable_f64_mixed() {
1425 let values = vec![Some(1.0), None, Some(3.0), None, Some(5.0)];
1426 let block = ColumnEncoder::encode_f64_nullable(&values, FloatEncoding::Plain).unwrap();
1427 assert_eq!(block.encoding, EncodingType::Nullable);
1428 let decoded = ColumnDecoder::decode(&block).unwrap();
1429 assert_eq!(decoded, DecodedColumn::NullableF64(values));
1430 }
1431
1432 #[test]
1433 fn nullable_f64_all_present() {
1434 let values = vec![Some(1.0), Some(2.0), Some(3.0)];
1435 let block = ColumnEncoder::encode_f64_nullable(&values, FloatEncoding::Plain).unwrap();
1436 let decoded = ColumnDecoder::decode(&block).unwrap();
1437 assert_eq!(decoded, DecodedColumn::NullableF64(values));
1438 }
1439
1440 #[test]
1441 fn nullable_f64_all_null() {
1442 let values: Vec<Option<f64>> = vec![None, None, None];
1443 let block = ColumnEncoder::encode_f64_nullable(&values, FloatEncoding::Plain).unwrap();
1444 let decoded = ColumnDecoder::decode(&block).unwrap();
1445 assert_eq!(decoded, DecodedColumn::NullableF64(values));
1446 }
1447
1448 #[test]
1449 fn nullable_i64_mixed() {
1450 let values = vec![Some(10), None, Some(30), Some(40), None];
1451 let block = ColumnEncoder::encode_i64_nullable(&values).unwrap();
1452 assert_eq!(block.encoding, EncodingType::Nullable);
1453 let decoded = ColumnDecoder::decode(&block).unwrap();
1454 assert_eq!(decoded, DecodedColumn::NullableI64(values));
1455 }
1456
1457 #[test]
1458 fn nullable_u64_mixed() {
1459 let values = vec![None, Some(100u64), Some(200), None];
1460 let block = ColumnEncoder::encode_u64_nullable(&values).unwrap();
1461 let decoded = ColumnDecoder::decode(&block).unwrap();
1462 assert_eq!(decoded, DecodedColumn::NullableU64(values));
1463 }
1464
1465 #[test]
1466 fn nullable_string_mixed() {
1467 let values = vec![Some("hello"), None, Some("world"), None];
1468 let block = ColumnEncoder::encode_string_nullable(&values).unwrap();
1469 let decoded = ColumnDecoder::decode(&block).unwrap();
1470 let expected: Vec<Option<String>> =
1471 values.iter().map(|v| v.map(ToString::to_string)).collect();
1472 assert_eq!(decoded, DecodedColumn::NullableString(expected));
1473 }
1474
1475 #[test]
1476 fn nullable_f64_bytes_roundtrip() {
1477 let values = vec![Some(1.0), None, Some(3.0)];
1478 let block = ColumnEncoder::encode_f64_nullable(&values, FloatEncoding::Plain).unwrap();
1479 let raw = block.to_bytes();
1480 let decoded = ColumnDecoder::decode_bytes(&raw).unwrap();
1481 assert_eq!(decoded, DecodedColumn::NullableF64(values));
1482 }
1483
1484 #[test]
1485 fn nullable_empty_errors() {
1486 let empty_f64: &[Option<f64>] = &[];
1487 assert!(ColumnEncoder::encode_f64_nullable(empty_f64, FloatEncoding::Plain).is_err());
1488 let empty_i64: &[Option<i64>] = &[];
1489 assert!(ColumnEncoder::encode_i64_nullable(empty_i64).is_err());
1490 let empty_u64: &[Option<u64>] = &[];
1491 assert!(ColumnEncoder::encode_u64_nullable(empty_u64).is_err());
1492 let empty_str: &[Option<&str>] = &[];
1493 assert!(ColumnEncoder::encode_string_nullable(empty_str).is_err());
1494 let empty_bool: &[Option<bool>] = &[];
1495 assert!(ColumnEncoder::encode_bool_nullable(empty_bool).is_err());
1496 }
1497
1498 #[test]
1499 fn nullable_distinguishes_zero_from_null() {
1500 let values = vec![Some(0.0), None, Some(0.0), None];
1503 let block = ColumnEncoder::encode_f64_nullable(&values, FloatEncoding::Plain).unwrap();
1504 let decoded = ColumnDecoder::decode(&block).unwrap();
1505 match &decoded {
1506 DecodedColumn::NullableF64(vals) => {
1507 assert_eq!(vals[0], Some(0.0));
1508 assert_eq!(vals[1], None); assert_eq!(vals[2], Some(0.0));
1510 assert_eq!(vals[3], None); }
1512 other => panic!("Expected NullableF64, got {other:?}"),
1513 }
1514 }
1515
1516 #[test]
1517 fn nullable_i64_distinguishes_zero_from_null() {
1518 let values = vec![Some(0i64), None, Some(0), None, Some(42)];
1519 let block = ColumnEncoder::encode_i64_nullable(&values).unwrap();
1520 let decoded = ColumnDecoder::decode(&block).unwrap();
1521 match &decoded {
1522 DecodedColumn::NullableI64(vals) => {
1523 assert_eq!(vals[0], Some(0));
1524 assert_eq!(vals[1], None);
1525 assert_eq!(vals[2], Some(0));
1526 assert_eq!(vals[3], None);
1527 assert_eq!(vals[4], Some(42));
1528 }
1529 other => panic!("Expected NullableI64, got {other:?}"),
1530 }
1531 }
1532}
1533
1534#[cfg(test)]
1535mod proptests {
1536 use super::*;
1537 use proptest::prelude::*;
1538
1539 proptest! {
1540 #[test]
1541 fn roundtrip_timestamps(mut values in proptest::collection::vec(any::<i64>(), 1..300)) {
1542 values.sort_unstable();
1543 let block = ColumnEncoder::encode_timestamps(&values).unwrap();
1544 let decoded = ColumnDecoder::decode(&block).unwrap();
1545 prop_assert_eq!(DecodedColumn::I64(values), decoded);
1546 }
1547
1548 #[test]
1549 fn roundtrip_i64(values in proptest::collection::vec(any::<i64>(), 1..300)) {
1550 let block = ColumnEncoder::encode_i64(&values).unwrap();
1551 let decoded = ColumnDecoder::decode(&block).unwrap();
1552 prop_assert_eq!(DecodedColumn::I64(values), decoded);
1553 }
1554
1555 #[test]
1556 fn roundtrip_u64(values in proptest::collection::vec(any::<u64>(), 1..300)) {
1557 let block = ColumnEncoder::encode_u64(&values).unwrap();
1558 let decoded = ColumnDecoder::decode(&block).unwrap();
1559 prop_assert_eq!(DecodedColumn::U64(values), decoded);
1560 }
1561
1562 #[test]
1563 fn roundtrip_f64(values in proptest::collection::vec(any::<f64>(), 1..300)) {
1564 let block = ColumnEncoder::encode_f64(&values, FloatEncoding::Gorilla).unwrap();
1565 let decoded = ColumnDecoder::decode(&block).unwrap();
1566 prop_assert_eq!(DecodedColumn::F64(values), decoded);
1567 }
1568
1569 #[test]
1570 fn roundtrip_bool(values in proptest::collection::vec(any::<bool>(), 1..300)) {
1571 let block = ColumnEncoder::encode_bool(&values).unwrap();
1572 let decoded = ColumnDecoder::decode(&block).unwrap();
1573 prop_assert_eq!(DecodedColumn::Bool(values), decoded);
1574 }
1575
1576 #[test]
1577 fn roundtrip_string(values in proptest::collection::vec("[a-z]{0,8}", 1..100)) {
1578 let refs: Vec<&str> = values.iter().map(String::as_str).collect();
1579 let block = ColumnEncoder::encode_string(&refs).unwrap();
1580 let decoded = ColumnDecoder::decode(&block).unwrap();
1581 prop_assert_eq!(DecodedColumn::String(values), decoded);
1582 }
1583
1584 #[test]
1585 fn roundtrip_nullable_i64(values in proptest::collection::vec(any::<Option<i64>>(), 1..300)) {
1586 let block = ColumnEncoder::encode_i64_nullable(&values).unwrap();
1587 let decoded = ColumnDecoder::decode(&block).unwrap();
1588 prop_assert_eq!(DecodedColumn::NullableI64(values), decoded);
1589 }
1590
1591 #[test]
1592 fn roundtrip_nullable_u64(values in proptest::collection::vec(any::<Option<u64>>(), 1..300)) {
1593 let block = ColumnEncoder::encode_u64_nullable(&values).unwrap();
1594 let decoded = ColumnDecoder::decode(&block).unwrap();
1595 prop_assert_eq!(DecodedColumn::NullableU64(values), decoded);
1596 }
1597
1598 #[test]
1599 fn roundtrip_nullable_f64(values in proptest::collection::vec(any::<Option<f64>>(), 1..300)) {
1600 let block = ColumnEncoder::encode_f64_nullable(&values, FloatEncoding::Gorilla).unwrap();
1601 let decoded = ColumnDecoder::decode(&block).unwrap();
1602 prop_assert_eq!(DecodedColumn::NullableF64(values), decoded);
1603 }
1604
1605 #[test]
1606 fn roundtrip_nullable_bool(values in proptest::collection::vec(any::<Option<bool>>(), 1..300)) {
1607 let block = ColumnEncoder::encode_bool_nullable(&values).unwrap();
1608 let decoded = ColumnDecoder::decode(&block).unwrap();
1609 prop_assert_eq!(DecodedColumn::NullableBool(values), decoded);
1610 }
1611
1612 #[test]
1613 fn roundtrip_nullable_string(values in proptest::collection::vec(
1614 proptest::option::of("[a-z]{0,8}"), 1..100
1615 )) {
1616 let refs: Vec<Option<&str>> = values.iter().map(|o| o.as_deref()).collect();
1617 let block = ColumnEncoder::encode_string_nullable(&refs).unwrap();
1618 let decoded = ColumnDecoder::decode(&block).unwrap();
1619 let expected: Vec<Option<String>> = values.clone();
1620 prop_assert_eq!(DecodedColumn::NullableString(expected), decoded);
1621 }
1622 }
1623}