1use std::time::Instant;
65
66use rudb_common::{Error, Result};
67
68use crate::chooser::{Chooser, EXHAUSTIVE, Settled};
69use crate::fsst::{MAX_SYMBOL_LEN, SymbolTable};
70use crate::integer;
71use crate::lz;
72use crate::reader::Reader;
73use crate::sequence::Sequence;
74use crate::tally::{self, Family};
75
76const MAX_DEPTH: u8 = 2;
79
80const SHARE_DIVISOR: usize = 20;
84
85const LZ_FLOOR: usize = 4096;
91
92pub(crate) const SAMPLE_BYTES: usize = 64 * 1024;
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum Kind {
102 Constant = 0,
104 Plain = 1,
106 Fsst = 2,
108 Dict = 3,
110 Front = 4,
112 Lz = 5,
115}
116
117impl Kind {
118 pub const ALL: [Self; 6] =
120 [Self::Constant, Self::Plain, Self::Fsst, Self::Dict, Self::Front, Self::Lz];
121
122 fn tag(self) -> u8 {
123 self as u8
124 }
125
126 fn from_tag(tag: u8) -> Result<Self> {
127 match tag {
128 0 => Ok(Self::Constant),
129 1 => Ok(Self::Plain),
130 2 => Ok(Self::Fsst),
131 3 => Ok(Self::Dict),
132 4 => Ok(Self::Front),
133 5 => Ok(Self::Lz),
134 other => Err(Error::internal(format!("unknown string encoding tag {other}"))),
135 }
136 }
137
138 #[must_use]
140 pub fn name(self) -> &'static str {
141 match self {
142 Self::Constant => "CONSTANT",
143 Self::Plain => "PLAIN",
144 Self::Fsst => "FSST",
145 Self::Dict => "DICT",
146 Self::Front => "FRONT",
147 Self::Lz => "LZ",
148 }
149 }
150}
151
152pub fn encode(values: &[&[u8]]) -> Result<Vec<u8>> {
163 encode_with(values, &EXHAUSTIVE)
164}
165
166pub fn encode_with(values: &[&[u8]], chooser: &dyn Chooser) -> Result<Vec<u8>> {
176 encode_at(values, 0, chooser)
177}
178
179#[derive(Debug, Clone, Default, PartialEq, Eq)]
192pub struct Flat {
193 bytes: Vec<u8>,
194 ends: Vec<usize>,
198}
199
200impl Flat {
201 fn with_capacity(count: usize, bytes: usize) -> Self {
202 Self { bytes: Vec::with_capacity(bytes), ends: Vec::with_capacity(count) }
203 }
204
205 fn push(&mut self, value: &[u8]) {
206 self.bytes.extend_from_slice(value);
207 self.ends.push(self.bytes.len());
208 }
209
210 fn start(&self, index: usize) -> usize {
212 if index == 0 { 0 } else { self.ends[index - 1] }
213 }
214
215 #[must_use]
217 pub fn len(&self) -> usize {
218 self.ends.len()
219 }
220
221 #[must_use]
223 pub fn is_empty(&self) -> bool {
224 self.ends.is_empty()
225 }
226
227 #[must_use]
230 pub fn bytes(&self) -> &[u8] {
231 &self.bytes
232 }
233
234 #[must_use]
236 pub fn get(&self, index: usize) -> Option<&[u8]> {
237 let end = *self.ends.get(index)?;
238 self.bytes.get(self.start(index)..end)
239 }
240
241 pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
243 let mut at = 0;
244 self.ends.iter().map(move |end| {
245 let value = self.bytes.get(at..*end).unwrap_or_default();
246 at = *end;
247 value
248 })
249 }
250
251 #[must_use]
253 pub fn into_bytes(self) -> Vec<u8> {
254 self.bytes
255 }
256
257 #[must_use]
264 pub fn into_parts(self) -> (Vec<u8>, Vec<usize>) {
265 (self.bytes, self.ends)
266 }
267
268 fn into_values(self) -> Vec<Vec<u8>> {
269 let mut values = Vec::with_capacity(self.len());
270 let mut at = 0;
271 for end in &self.ends {
272 values.push(self.bytes[at..*end].to_vec());
273 at = *end;
274 }
275 values
276 }
277}
278
279pub fn decode_flat(bytes: &[u8]) -> Result<Flat> {
285 let mut reader = Reader::new(bytes);
286 let flat = decode_chunk(&mut reader)?;
287 if reader.remaining() != 0 {
288 return Err(Error::internal(format!(
289 "{} bytes left over after decoding a string chunk",
290 reader.remaining()
291 )));
292 }
293 Ok(flat)
294}
295
296pub fn holds_in(bytes: &[u8], sequence: &Sequence) -> Result<Option<Vec<bool>>> {
307 holds_in_where(bytes, sequence, |_| true)
308}
309
310pub fn holds_in_where(
319 bytes: &[u8],
320 sequence: &Sequence,
321 mut maybe: impl FnMut(usize) -> bool,
322) -> Result<Option<Vec<bool>>> {
323 if bytes.first() != Some(&Kind::Fsst.tag()) {
324 return Ok(None);
325 }
326 let mut reader = Reader::new(bytes);
327 reader.u8()?;
328 let count = reader.u32()? as usize;
329 let runs = read_compressed(&mut reader, count)?;
330 let mut coded = sequence.over(&runs.table);
331 let mut held = Vec::with_capacity(count);
332 let mut payload = runs.payload;
333 for &run in &runs.lengths {
334 let Some((codes, rest)) = payload.split_at_checked(run) else {
335 return Err(Error::internal("a compressed run is past the end of its chunk"));
336 };
337 payload = rest;
338 let row = held.len();
339 held.push(maybe(row) && coded.holds(codes)?);
340 }
341 if reader.remaining() != 0 {
342 return Err(Error::internal(format!(
343 "{} bytes left over after decoding a string chunk",
344 reader.remaining()
345 )));
346 }
347 Ok(Some(held))
348}
349
350pub fn decode_flat_at(bytes: &[u8], positions: &[u32]) -> Result<Flat> {
362 if positions.windows(2).any(|pair| pair[0] >= pair[1]) {
363 return Err(Error::internal("the positions to decode do not rise"));
364 }
365 let mut reader = Reader::new(bytes);
366 let flat = if bytes.first() == Some(&Kind::Fsst.tag()) {
367 reader.u8()?;
368 let count = reader.u32()? as usize;
369 let runs = read_compressed(&mut reader, count)?;
370 let mut flat = Flat::with_capacity(positions.len(), runs.payload.len());
371 let mut at = 0;
372 let mut next = 0;
373 for &position in positions {
374 let position = position as usize;
375 if position >= count {
376 return Err(Error::internal(format!("value {position} is not in the chunk")));
377 }
378 at += runs.lengths[next..position].iter().sum::<usize>();
379 runs.run_into(position, &mut at, &mut flat.bytes)?;
380 flat.ends.push(flat.bytes.len());
381 next = position + 1;
382 }
383 flat
384 } else {
385 let whole = decode_chunk(&mut reader)?;
386 let mut flat = Flat::with_capacity(positions.len(), 0);
387 for &position in positions {
388 let value = whole
389 .get(position as usize)
390 .ok_or_else(|| Error::internal(format!("value {position} is not in the chunk")))?;
391 flat.push(value);
392 }
393 flat
394 };
395 if reader.remaining() != 0 {
396 return Err(Error::internal(format!(
397 "{} bytes left over after decoding a string chunk",
398 reader.remaining()
399 )));
400 }
401 Ok(flat)
402}
403
404pub fn decode_prefix(bytes: &[u8]) -> Result<(Vec<Vec<u8>>, usize)> {
413 let mut reader = Reader::new(bytes);
414 let values = decode_chunk(&mut reader)?;
415 Ok((values.into_values(), reader.used()))
416}
417
418pub fn describe_prefix(bytes: &[u8]) -> Result<(String, usize)> {
424 let mut reader = Reader::new(bytes);
425 let text = describe_chunk(&mut reader)?;
426 Ok((text, reader.used()))
427}
428
429pub fn decode(bytes: &[u8]) -> Result<Vec<Vec<u8>>> {
435 Ok(decode_flat(bytes)?.into_values())
436}
437
438pub fn candidate_sizes(values: &[&[u8]]) -> Result<Vec<(Kind, usize)>> {
445 let mut sizes = Vec::new();
446 for kind in candidates(values, 0) {
447 if let Some(bytes) = encode_as(kind, values, 0, &EXHAUSTIVE)? {
448 sizes.push((kind, bytes.len()));
449 }
450 }
451 Ok(sizes)
452}
453
454#[must_use]
461pub fn offered(values: &[&[u8]]) -> Vec<Kind> {
462 candidates(values, 0)
463}
464
465pub fn encode_only(kind: Kind, values: &[&[u8]]) -> Result<Option<Vec<u8>>> {
477 encode_as(kind, values, 0, &EXHAUSTIVE)
478}
479
480#[must_use]
490pub fn fsst_shape(values: &[&[u8]]) -> Settled {
491 let started = Instant::now();
492 let table = SymbolTable::train(&sample_of(values));
493 tally::chose(Family::String, started);
494 Settled::new(vec![Kind::Fsst], Vec::new()).with_symbols(0, table)
495}
496
497pub fn encode_fsst(values: &[&[u8]], shape: &Settled) -> Result<Option<Vec<u8>>> {
507 let out =
508 tally::offer(Family::String, Kind::Fsst.tag(), || encode_as(Kind::Fsst, values, 0, shape))?;
509 if out.is_some() {
510 tally::kept(Family::String, Kind::Fsst.tag());
511 }
512 Ok(out)
513}
514
515pub(crate) fn size_as(kind: Kind, values: &[&[u8]], depth: u8) -> Result<Option<usize>> {
520 Ok(encode_as(kind, values, depth, &EXHAUSTIVE)?.map(|bytes| bytes.len()))
521}
522
523pub fn describe(bytes: &[u8]) -> Result<String> {
529 let mut reader = Reader::new(bytes);
530 describe_chunk(&mut reader)
531}
532
533#[must_use]
545pub fn with_symbols(shape: Settled, blocks: &[Vec<&[u8]>]) -> Settled {
546 let kinds = shape.strings();
547 let Some(depth) =
548 (0..=kinds.len()).find(|&at| matches!(kinds.get(at), Some(Kind::Fsst) | None))
549 else {
550 return shape;
551 };
552 let leads =
553 kinds[..depth].iter().all(|kind| matches!(kind, Kind::Front | Kind::Lz | Kind::Dict));
554 if !leads || depth > usize::from(MAX_DEPTH) {
555 return shape;
556 }
557 let mut reached: Vec<Vec<u8>> = Vec::new();
558 for block in blocks {
559 let mut values: Vec<Vec<u8>> = block.iter().map(|value| value.to_vec()).collect();
560 for kind in &kinds[..depth] {
561 let refs: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
562 values = match kind {
563 Kind::Front => front_code(&refs).1.into_iter().map(<[u8]>::to_vec).collect(),
564 Kind::Dict => dictionary_of(&refs).0.into_iter().map(<[u8]>::to_vec).collect(),
565 _ => {
566 let joined = refs.concat();
567 lz::tokens_of(&joined).literals.into_iter().map(<[u8]>::to_vec).collect()
568 }
569 };
570 }
571 reached.extend(values);
572 }
573 let refs: Vec<&[u8]> = reached.iter().map(Vec::as_slice).collect();
574 let table = SymbolTable::train(&sample_of(&refs));
575 if table.is_empty() {
576 return shape;
577 }
578 shape.with_symbols(depth as u8, table)
579}
580
581fn encode_at(values: &[&[u8]], depth: u8, chooser: &dyn Chooser) -> Result<Vec<u8>> {
582 let started = Instant::now();
583 let offered = candidates(values, depth);
584 let narrowed = chooser.narrow_strings(values, &offered, depth);
585 let counted = depth == 0;
587 if counted {
588 tally::chose(Family::String, started);
589 }
590 let mut best: Option<(Kind, Vec<u8>)> = None;
591 for kind in narrowed {
592 let encoded = if counted {
593 tally::offer(Family::String, kind.tag(), || encode_as(kind, values, depth, chooser))?
594 } else {
595 encode_as(kind, values, depth, chooser)?
596 };
597 let Some(bytes) = encoded else {
598 continue;
599 };
600 if best.as_ref().is_none_or(|(_, current)| bytes.len() < current.len()) {
601 best = Some((kind, bytes));
602 }
603 }
604 let (kind, bytes) =
605 best.ok_or_else(|| Error::internal("no string encoding applied to the chunk"))?;
606 if counted {
607 tally::kept(Family::String, kind.tag());
608 }
609 Ok(bytes)
610}
611
612fn candidates(values: &[&[u8]], depth: u8) -> Vec<Kind> {
613 let mut kinds = vec![Kind::Plain];
614 if values.is_empty() {
615 return kinds;
616 }
617 if values.iter().all(|value| *value == values[0]) {
618 return vec![Kind::Constant];
619 }
620 kinds.push(Kind::Fsst);
621 if depth < MAX_DEPTH && has_duplicates(values) {
622 kinds.push(Kind::Dict);
623 }
624 if depth < MAX_DEPTH && sharing_of(values) >= total_len(values) / SHARE_DIVISOR {
625 kinds.push(Kind::Front);
626 }
627 if depth < MAX_DEPTH && total_len(values) >= LZ_FLOOR {
628 kinds.push(Kind::Lz);
629 }
630 kinds
631}
632
633fn sharing_of(values: &[&[u8]]) -> usize {
640 let mut shared = 0;
641 for pair in values.windows(2) {
642 shared += shared_prefix(pair[0], pair[1]);
643 }
644 shared
645}
646
647pub(crate) fn front_code<'a>(values: &[&'a [u8]]) -> (Vec<i64>, Vec<&'a [u8]>) {
653 let mut prefixes = Vec::with_capacity(values.len());
654 let mut suffixes: Vec<&'a [u8]> = Vec::with_capacity(values.len());
655 let mut previous: &[u8] = b"";
656 for value in values {
657 let value: &'a [u8] = value;
658 let shared = shared_prefix(previous, value);
659 prefixes.push(shared as i64);
660 suffixes.push(&value[shared..]);
661 previous = value;
662 }
663 (prefixes, suffixes)
664}
665
666pub(crate) fn front_decode(prefixes: &[i64], suffixes: Vec<Vec<u8>>) -> Result<Vec<Vec<u8>>> {
673 let mut values: Vec<Vec<u8>> = Vec::with_capacity(suffixes.len());
674 for (index, suffix) in suffixes.into_iter().enumerate() {
675 let shared = usize::try_from(prefixes[index])
676 .map_err(|_| Error::internal("a negative shared prefix length"))?;
677 let previous: &[u8] = if index == 0 { b"" } else { &values[index - 1] };
678 if shared > previous.len() {
679 return Err(Error::internal(format!(
680 "a value shares {shared} bytes with a value {} bytes long",
681 previous.len()
682 )));
683 }
684 let mut value = Vec::with_capacity(shared + suffix.len());
685 value.extend_from_slice(&previous[..shared]);
686 value.extend_from_slice(&suffix);
687 values.push(value);
688 }
689 Ok(values)
690}
691
692fn shared_prefix(previous: &[u8], value: &[u8]) -> usize {
693 let limit = previous.len().min(value.len());
694 let mut shared = 0;
695 while shared < limit && previous[shared] == value[shared] {
696 shared += 1;
697 }
698 shared
699}
700
701fn total_len(values: &[&[u8]]) -> usize {
702 values.iter().map(|value| value.len()).sum()
703}
704
705fn encode_as(
706 kind: Kind,
707 values: &[&[u8]],
708 depth: u8,
709 chooser: &dyn Chooser,
710) -> Result<Option<Vec<u8>>> {
711 let mut out = vec![kind.tag()];
712 put_u32(&mut out, u32::try_from(values.len()).map_err(|_| too_long(values.len()))?);
713 match kind {
714 Kind::Constant => {
715 let Some(first) = values.first() else {
716 return Ok(None);
717 };
718 if values.iter().any(|value| value != first) {
719 return Ok(None);
720 }
721 put_u32(&mut out, u32::try_from(first.len()).map_err(|_| too_long(first.len()))?);
722 out.extend_from_slice(first);
723 }
724 Kind::Plain => {
725 out.extend_from_slice(&encode_lengths(values, chooser)?);
726 for value in values {
727 out.extend_from_slice(value);
728 }
729 }
730 Kind::Fsst => {
731 let trained;
732 let table = match chooser.symbols(depth) {
733 Some(table) => table,
734 None => {
735 trained = SymbolTable::train(&sample_of(values));
736 &trained
737 }
738 };
739 if table.is_empty() {
740 return Ok(None);
741 }
742 let mut compressed = Vec::new();
743 let mut lengths = Vec::with_capacity(values.len());
744 for value in values {
745 let before = compressed.len();
746 table.compress(value, &mut compressed);
747 lengths.push((compressed.len() - before) as i64);
748 }
749 table.serialize(&mut out);
750 out.extend_from_slice(&integer::encode_with(&lengths, chooser)?);
751 out.extend_from_slice(&compressed);
752 }
753 Kind::Dict => {
754 let (entries, codes) = dictionary_of(values);
755 if entries.is_empty() {
756 return Ok(None);
757 }
758 out.extend_from_slice(&encode_at(&entries, depth + 1, chooser)?);
759 out.extend_from_slice(&integer::encode_with(&codes, chooser)?);
760 }
761 Kind::Front => {
762 let (prefixes, suffixes) = front_code(values);
763 out.extend_from_slice(&integer::encode_with(&prefixes, chooser)?);
764 out.extend_from_slice(&encode_at(&suffixes, depth + 1, chooser)?);
765 }
766 Kind::Lz => {
767 let mut joined = Vec::with_capacity(total_len(values));
768 let mut sizes = Vec::with_capacity(values.len());
769 for value in values {
770 joined.extend_from_slice(value);
771 sizes.push(value.len() as i64);
772 }
773 let tokens = lz::tokens_of(&joined);
774 out.extend_from_slice(&integer::encode_with(&sizes, chooser)?);
775 out.extend_from_slice(&integer::encode_with(&tokens.lengths, chooser)?);
776 out.extend_from_slice(&integer::encode_with(&tokens.offsets, chooser)?);
777 out.extend_from_slice(&encode_at(&tokens.literals, depth + 1, chooser)?);
778 }
779 }
780 Ok(Some(out))
781}
782
783fn decode_chunk(reader: &mut Reader<'_>) -> Result<Flat> {
784 let kind = Kind::from_tag(reader.u8()?)?;
785 let count = reader.u32()? as usize;
786 match kind {
787 Kind::Constant => {
788 let len = reader.u32()? as usize;
789 let value = reader.bytes(len)?;
790 let mut flat = Flat::with_capacity(count, len.saturating_mul(count));
791 for _ in 0..count {
792 flat.push(value);
793 }
794 Ok(flat)
795 }
796 Kind::Plain => {
797 let lengths = decode_lengths(reader, count)?;
798 let total = sum_of(&lengths)?;
801 let payload = reader.bytes(total)?;
802 let mut flat = Flat::with_capacity(count, total);
803 flat.bytes.extend_from_slice(payload);
804 let mut at = 0;
805 for length in lengths {
806 at += length;
807 flat.ends.push(at);
808 }
809 Ok(flat)
810 }
811 Kind::Fsst => {
812 let runs = read_compressed(reader, count)?;
813 let mut flat = Flat::with_capacity(count, 0);
814 runs.all_into(&mut flat)?;
815 Ok(flat)
816 }
817 Kind::Dict => {
818 let dictionary = decode_chunk(reader)?;
819 let codes = decode_integers(reader)?;
820 if codes.len() != count {
821 return Err(Error::internal(format!(
822 "a dictionary chunk says it holds {count} values and has {} codes",
823 codes.len()
824 )));
825 }
826 let mut flat = Flat::with_capacity(count, dictionary.bytes.len());
827 for code in codes {
828 let entry =
829 usize::try_from(code).ok().and_then(|index| dictionary.get(index)).ok_or_else(
830 || Error::internal(format!("code {code} is not in the dictionary")),
831 )?;
832 flat.push(entry);
833 }
834 Ok(flat)
835 }
836 Kind::Front => {
837 let prefixes = decode_integers(reader)?;
838 let suffixes = decode_chunk(reader)?;
839 if prefixes.len() != count || suffixes.len() != count {
840 return Err(Error::internal(format!(
841 "a front coded chunk says it holds {count} values and has {} prefixes and {} suffixes",
842 prefixes.len(),
843 suffixes.len()
844 )));
845 }
846 let mut room = 0usize;
853 let mut previous = 0usize;
854 for (index, prefix) in prefixes.iter().enumerate() {
855 let shared = usize::try_from(*prefix)
856 .map_err(|_| Error::internal("a negative shared prefix length"))?;
857 if shared > previous {
858 return Err(Error::internal(format!(
859 "a value shares {shared} bytes with a value {previous} bytes long"
860 )));
861 }
862 previous = shared + suffixes.get(index).map_or(0, <[u8]>::len);
863 room = room
864 .checked_add(previous)
865 .ok_or_else(|| Error::internal("a string chunk longer than memory"))?;
866 }
867 let mut flat = Flat::with_capacity(count, room);
868 for (index, &prefix) in prefixes.iter().enumerate() {
869 let shared = prefix as usize;
870 let from = if index == 0 { 0 } else { flat.start(index - 1) };
871 flat.bytes.extend_from_within(from..from + shared);
872 flat.bytes.extend_from_slice(suffixes.get(index).expect("in range"));
873 flat.ends.push(flat.bytes.len());
874 }
875 Ok(flat)
876 }
877 Kind::Lz => {
878 let sizes = decode_integers(reader)?;
879 let lengths = decode_integers(reader)?;
880 let offsets = decode_integers(reader)?;
881 if sizes.len() != count {
882 return Err(Error::internal(format!(
883 "a matched chunk says it holds {count} values and has {} lengths",
884 sizes.len()
885 )));
886 }
887 let mut total = 0usize;
888 let mut widths = Vec::with_capacity(count);
889 for size in sizes {
890 let width = usize::try_from(size)
891 .map_err(|_| Error::internal("a negative string length"))?;
892 total = total
893 .checked_add(width)
894 .ok_or_else(|| Error::internal("a string chunk longer than memory"))?;
895 widths.push(width);
896 }
897 let mut flat = Flat::with_capacity(count, total.saturating_add(REPLAY_SLACK));
902 replay_literals(reader, &lengths, &offsets, total, &mut flat.bytes)?;
903 if flat.bytes.len() != total {
904 return Err(Error::internal(format!(
905 "a matched chunk rebuilt {} bytes where its lengths add up to {total}",
906 flat.bytes.len()
907 )));
908 }
909 let mut at = 0;
910 for width in widths {
911 at += width;
912 flat.ends.push(at);
913 }
914 Ok(flat)
915 }
916 }
917}
918
919struct Compressed<'a> {
925 table: SymbolTable,
927 lengths: Vec<usize>,
929 payload: &'a [u8],
931}
932
933impl Compressed<'_> {
934 fn run_into(&self, index: usize, at: &mut usize, out: &mut Vec<u8>) -> Result<()> {
943 self.table.decompress(self.run(index, at)?, out)
944 }
945
946 fn all_into(&self, flat: &mut Flat) -> Result<()> {
959 let out = &mut flat.bytes;
960 let mut at = out.len();
961 let mut payload = self.payload;
962 for &run in &self.lengths {
963 let Some((codes, rest)) = payload.split_at_checked(run) else {
964 return Err(Error::internal("a compressed run is past the end of its chunk"));
965 };
966 payload = rest;
967 let need = run
968 .checked_mul(MAX_SYMBOL_LEN)
969 .and_then(|room| room.checked_add(at))
970 .ok_or_else(|| Error::internal("a compressed chunk longer than memory"))?;
971 if out.len() < need {
972 out.resize(need.max(out.len() * 2), 0);
973 }
974 at = self.table.decompress_at(codes, out, at)?;
975 flat.ends.push(at);
976 }
977 out.truncate(at);
978 Ok(())
979 }
980
981 fn run(&self, index: usize, at: &mut usize) -> Result<&[u8]> {
984 let length = *self
985 .lengths
986 .get(index)
987 .ok_or_else(|| Error::internal(format!("run {index} is not in the chunk")))?;
988 let end = at
989 .checked_add(length)
990 .ok_or_else(|| Error::internal("a compressed chunk longer than memory"))?;
991 let run = self
992 .payload
993 .get(*at..end)
994 .ok_or_else(|| Error::internal("a compressed run is past the end of its chunk"))?;
995 *at = end;
996 Ok(run)
997 }
998}
999
1000fn read_compressed<'a>(reader: &mut Reader<'a>, count: usize) -> Result<Compressed<'a>> {
1009 let (table, used) = SymbolTable::deserialize(reader.rest())?;
1010 reader.skip(used)?;
1011 let lengths = decode_lengths(reader, count)?;
1012 let compressed_len = sum_of(&lengths)?;
1015 if compressed_len > reader.remaining() {
1016 return Err(Error::internal(format!(
1017 "a compressed chunk says it holds {compressed_len} bytes and has {}",
1018 reader.remaining()
1019 )));
1020 }
1021 let payload = reader.bytes(compressed_len)?;
1022 Ok(Compressed { table, lengths, payload })
1023}
1024
1025fn replay_literals(
1038 reader: &mut Reader<'_>,
1039 lengths: &[i64],
1040 offsets: &[i64],
1041 total: usize,
1042 out: &mut Vec<u8>,
1043) -> Result<()> {
1044 if reader.rest().first() == Some(&Kind::Fsst.tag()) {
1045 reader.u8()?;
1046 let runs = reader.u32()? as usize;
1047 let compressed = read_compressed(reader, runs)?;
1048 return replay_in_place(&compressed, lengths, offsets, total, out);
1049 }
1050 let literals = decode_chunk(reader)?;
1051 lz::rebuild_into(&literals, lengths, offsets, out)
1052}
1053
1054const REPLAY_SLACK: usize = 16;
1060
1061fn replay_in_place(
1073 compressed: &Compressed<'_>,
1074 lengths: &[i64],
1075 offsets: &[i64],
1076 total: usize,
1077 out: &mut Vec<u8>,
1078) -> Result<()> {
1079 let runs = compressed.lengths.len();
1080 if runs != lengths.len() || lengths.len() != offsets.len() {
1081 return Err(Error::internal(format!(
1082 "a matched chunk has {runs} literal runs, {} lengths and {} offsets",
1083 lengths.len(),
1084 offsets.len()
1085 )));
1086 }
1087 let base = out.len();
1088 let room = total
1089 .checked_add(REPLAY_SLACK)
1090 .ok_or_else(|| Error::internal("a string chunk longer than memory"))?;
1091 out.resize(base + room, 0);
1092 let mut payload = compressed.payload;
1093 let mut at = base;
1094 for ((&run, &length), &offset) in compressed.lengths.iter().zip(lengths).zip(offsets) {
1095 let Some((codes, rest)) = payload.split_at_checked(run) else {
1096 return Err(Error::internal("a compressed run is past the end of its chunk"));
1097 };
1098 payload = rest;
1099 at = compressed.table.decompress_at(codes, out, at)?;
1100 let length =
1101 usize::try_from(length).map_err(|_| Error::internal("a negative copy length"))?;
1102 if length == 0 {
1103 continue;
1104 }
1105 let offset =
1106 usize::try_from(offset).map_err(|_| Error::internal("a negative copy offset"))?;
1107 at = copy_back(out, base, at, offset, length)?;
1108 }
1109 if at > base + total {
1110 return Err(Error::internal(format!(
1111 "a matched chunk rebuilt {} bytes where its lengths add up to {total}",
1112 at - base
1113 )));
1114 }
1115 out.truncate(at);
1116 Ok(())
1117}
1118
1119fn copy_back(
1127 out: &mut [u8],
1128 base: usize,
1129 at: usize,
1130 offset: usize,
1131 length: usize,
1132) -> Result<usize> {
1133 if offset == 0 || offset > at - base {
1134 return Err(Error::internal(format!(
1135 "a copy reaches {offset} bytes back into {} bytes of output",
1136 at - base
1137 )));
1138 }
1139 let end = at
1140 .checked_add(length)
1141 .filter(|&end| end <= out.len())
1142 .ok_or_else(|| Error::internal("a matched chunk rebuilds more than its lengths say"))?;
1143 let from = at - offset;
1144 let wide = end + REPLAY_SLACK <= out.len();
1145 if wide && offset >= 16 {
1146 let mut step = 0;
1147 while step < length {
1148 out.copy_within(from + step..from + step + 16, at + step);
1149 step += 16;
1150 }
1151 } else if wide && offset >= 8 {
1152 let mut step = 0;
1153 while step < length {
1154 out.copy_within(from + step..from + step + 8, at + step);
1155 step += 8;
1156 }
1157 } else {
1158 for step in 0..length {
1159 out[at + step] = out[from + step];
1160 }
1161 }
1162 Ok(end)
1163}
1164
1165fn describe_chunk(reader: &mut Reader<'_>) -> Result<String> {
1166 let kind = Kind::from_tag(reader.u8()?)?;
1167 let count = reader.u32()? as usize;
1168 Ok(match kind {
1169 Kind::Constant => {
1170 let len = reader.u32()? as usize;
1171 reader.bytes(len)?;
1172 "CONSTANT".to_string()
1173 }
1174 Kind::Plain => {
1175 let (shape, lengths) = describe_lengths(reader, count)?;
1176 reader.skip(lengths.iter().sum())?;
1177 format!("PLAIN({shape})")
1178 }
1179 Kind::Fsst => {
1180 let (table, used) = SymbolTable::deserialize(reader.rest())?;
1181 reader.skip(used)?;
1182 let (shape, lengths) = describe_lengths(reader, count)?;
1183 reader.skip(lengths.iter().sum())?;
1184 format!("FSST[{}]({shape})", table.len())
1185 }
1186 Kind::Dict => {
1187 let entries = describe_chunk(reader)?;
1188 let codes = describe_integers(reader)?;
1189 format!("DICT({entries}, {codes})")
1190 }
1191 Kind::Front => {
1192 let prefixes = describe_integers(reader)?;
1193 let suffixes = describe_chunk(reader)?;
1194 format!("FRONT({prefixes}, {suffixes})")
1195 }
1196 Kind::Lz => {
1197 let sizes = describe_integers(reader)?;
1198 let lengths = describe_integers(reader)?;
1199 let offsets = describe_integers(reader)?;
1200 let literals = describe_chunk(reader)?;
1201 format!("LZ({sizes}, {lengths}, {offsets}, {literals})")
1202 }
1203 })
1204}
1205
1206fn describe_lengths(reader: &mut Reader<'_>, count: usize) -> Result<(String, Vec<usize>)> {
1210 let (shape, _) = integer::describe_prefix(reader.rest())?;
1211 let lengths = decode_lengths(reader, count)?;
1212 Ok((shape, lengths))
1213}
1214
1215fn encode_lengths(values: &[&[u8]], chooser: &dyn Chooser) -> Result<Vec<u8>> {
1216 let lengths: Vec<i64> = values.iter().map(|value| value.len() as i64).collect();
1217 integer::encode_with(&lengths, chooser)
1218}
1219
1220fn decode_lengths(reader: &mut Reader<'_>, count: usize) -> Result<Vec<usize>> {
1221 let lengths = decode_integers(reader)?;
1222 if lengths.len() != count {
1223 return Err(Error::internal(format!(
1224 "a string chunk says it holds {count} values and has {} lengths",
1225 lengths.len()
1226 )));
1227 }
1228 lengths
1229 .into_iter()
1230 .map(|length| {
1231 usize::try_from(length).map_err(|_| Error::internal("a negative string length"))
1232 })
1233 .collect()
1234}
1235
1236fn sum_of(lengths: &[usize]) -> Result<usize> {
1242 lengths
1243 .iter()
1244 .try_fold(0usize, |total, length| total.checked_add(*length))
1245 .ok_or_else(|| Error::internal("a string chunk longer than memory"))
1246}
1247
1248fn decode_integers(reader: &mut Reader<'_>) -> Result<Vec<i64>> {
1252 let (values, used) = integer::decode_prefix(reader.rest())?;
1253 reader.skip(used)?;
1254 Ok(values)
1255}
1256
1257fn describe_integers(reader: &mut Reader<'_>) -> Result<String> {
1258 let (text, used) = integer::describe_prefix(reader.rest())?;
1259 reader.skip(used)?;
1260 Ok(text)
1261}
1262
1263pub(crate) fn sample_of<'a>(values: &[&'a [u8]]) -> Vec<&'a [u8]> {
1282 sample_bytes_of(values, SAMPLE_BYTES)
1283}
1284
1285pub(crate) fn sample_bytes_of<'a>(values: &[&'a [u8]], budget: usize) -> Vec<&'a [u8]> {
1288 let budget = budget.max(1);
1289 let total: usize = values.iter().map(|value| value.len()).sum();
1290 if total <= budget {
1291 return values.to_vec();
1292 }
1293 let stride = total.div_ceil(budget).max(1);
1294 let span = (stride * 2 - 1).max(1) as u64;
1295 let mut state = 0x2545_f491_4f6c_dd1du64;
1296 let mut sample = Vec::with_capacity(values.len() / stride + 1);
1297 let mut at = 0usize;
1298 while at < values.len() {
1299 sample.push(values[at]);
1300 state ^= state << 13;
1301 state ^= state >> 7;
1302 state ^= state << 17;
1303 at += 1 + (state % span) as usize;
1304 }
1305 sample
1306}
1307
1308fn dictionary_of<'a>(values: &[&'a [u8]]) -> (Vec<&'a [u8]>, Vec<i64>) {
1320 let mut order: Vec<u32> = (0..values.len() as u32).collect();
1321 order.sort_unstable_by(|left, right| values[*left as usize].cmp(values[*right as usize]));
1322 let mut entries: Vec<&'a [u8]> = Vec::new();
1323 let mut codes = vec![0i64; values.len()];
1324 for &index in &order {
1325 let value = values[index as usize];
1326 if entries.last() != Some(&value) {
1327 entries.push(value);
1328 }
1329 codes[index as usize] = (entries.len() - 1) as i64;
1330 }
1331 (entries, codes)
1332}
1333
1334fn has_duplicates(values: &[&[u8]]) -> bool {
1344 let Some(slots) = values.len().checked_mul(2).map(usize::next_power_of_two) else {
1345 return false;
1346 };
1347 let mask = slots - 1;
1348 let mut table = vec![u32::MAX; slots];
1349 for (index, value) in values.iter().enumerate() {
1350 let mut at = hash_of(value) as usize & mask;
1351 loop {
1352 let held = table[at];
1353 if held == u32::MAX {
1354 table[at] = index as u32;
1355 break;
1356 }
1357 if values[held as usize] == *value {
1358 return true;
1359 }
1360 at = (at + 1) & mask;
1361 }
1362 }
1363 false
1364}
1365
1366fn hash_of(value: &[u8]) -> u64 {
1373 let mut hash = 0xcbf2_9ce4_8422_2325_u64;
1374 let mut chunks = value.chunks_exact(8);
1375 for chunk in &mut chunks {
1376 let word = u64::from_le_bytes(chunk.try_into().expect("chunks_exact(8) gives eight bytes"));
1377 hash = (hash ^ word).wrapping_mul(0x1_0000_01b3);
1378 }
1379 for byte in chunks.remainder() {
1380 hash = (hash ^ u64::from(*byte)).wrapping_mul(0x1_0000_01b3);
1381 }
1382 (hash ^ (value.len() as u64)).wrapping_mul(0x1_0000_01b3)
1383}
1384
1385fn too_long(len: usize) -> Error {
1386 Error::internal(format!("a string chunk of {len} is longer than the format allows"))
1387}
1388
1389fn put_u32(out: &mut Vec<u8>, value: u32) {
1390 out.extend_from_slice(&value.to_le_bytes());
1391}
1392
1393#[cfg(test)]
1394mod tests {
1395 use super::*;
1396
1397 fn urls(count: usize) -> Vec<Vec<u8>> {
1398 let hosts = ["www.example.com", "shop.example.com", "news.other.example.org"];
1399 let paths = ["/index.html", "/catalog/item", "/search", "/user/profile/settings"];
1400 (0..count)
1401 .map(|index| {
1402 let host = hosts[index % hosts.len()];
1403 let path = paths[(index / 3) % paths.len()];
1404 format!("http://{host}{path}?session={}&ref=google", index * 7).into_bytes()
1405 })
1406 .collect()
1407 }
1408
1409 #[test]
1412 fn the_values_at_some_positions_are_the_ones_a_whole_decode_has_there() {
1413 let values = urls(1000);
1414 let refs: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
1415 let positions = [0_u32, 3, 4, 500, 998, 999];
1416 let wanted: Vec<Vec<u8>> =
1417 positions.iter().map(|&at| values[at as usize].clone()).collect();
1418 for kind in offered(&refs) {
1419 let Some(encoded) = encode_only(kind, &refs).expect("encoded") else { continue };
1420 let flat = decode_flat_at(&encoded, &positions).expect("decoded");
1421 assert_eq!(flat.into_values(), wanted, "{kind:?}");
1422 let none = decode_flat_at(&encoded, &[]).expect("decoded");
1423 assert!(none.is_empty(), "{kind:?}");
1424 assert!(decode_flat_at(&encoded, &[4, 3]).is_err(), "{kind:?}");
1425 assert!(decode_flat_at(&encoded, &[1000]).is_err(), "{kind:?}");
1426 }
1427 let fsst = encode_only(Kind::Fsst, &refs).expect("encoded").expect("compressible");
1428 assert_eq!(decode_flat_at(&fsst, &positions).expect("decoded").into_values(), wanted);
1429 }
1430
1431 fn front_lz() -> Settled {
1432 Settled::new(vec![Kind::Front, Kind::Lz], vec![integer::Kind::Packed])
1433 }
1434
1435 #[test]
1439 fn a_block_compressed_against_the_column_table_reads_back() {
1440 let values = urls(4096);
1441 let refs: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
1442 let blocks: Vec<Vec<&[u8]>> = refs.chunks(1024).take(2).map(<[&[u8]]>::to_vec).collect();
1443 let shape = with_symbols(front_lz(), &blocks);
1444 assert!(shape.symbols(2).is_some(), "FRONT then LZ leaves FSST the third level");
1445 assert!(shape.symbols(1).is_none(), "and only that one");
1446 for block in refs.chunks(1024) {
1447 let encoded = encode_with(block, &shape).expect("encoded");
1448 assert_eq!(decode(&encoded).expect("decoded"), block.to_vec());
1449 }
1450 }
1451
1452 #[test]
1454 fn a_shape_ending_in_plain_gets_no_table() {
1455 let values = urls(1024);
1456 let refs: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
1457 let plain = Settled::new(vec![Kind::Lz, Kind::Plain], vec![integer::Kind::Packed]);
1458 let shape = with_symbols(plain, std::slice::from_ref(&refs));
1459 assert!((0..=MAX_DEPTH).all(|depth| shape.symbols(depth).is_none()));
1460 let fsst = Settled::new(vec![Kind::Fsst], vec![integer::Kind::Packed]);
1461 assert!(with_symbols(fsst, &[refs]).symbols(0).is_some());
1462 }
1463
1464 fn keyed(values: Vec<Vec<u8>>) -> Vec<Vec<u8>> {
1468 values
1469 .into_iter()
1470 .enumerate()
1471 .map(|(index, value)| {
1472 let key = (index as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15) % 1_000_000_007;
1473 let mut out = format!("{key:010}/").into_bytes();
1474 out.extend_from_slice(&value);
1475 out
1476 })
1477 .collect()
1478 }
1479
1480 fn borrow(values: &[Vec<u8>]) -> Vec<&[u8]> {
1481 values.iter().map(Vec::as_slice).collect()
1482 }
1483
1484 fn round_trip(values: &[Vec<u8>]) -> Vec<u8> {
1485 let borrowed = borrow(values);
1486 let bytes = encode(&borrowed).unwrap();
1487 let back = decode(&bytes).unwrap();
1488 assert_eq!(back, values, "{}", describe(&bytes).unwrap());
1489 check_flat(&bytes, values);
1490 bytes
1491 }
1492
1493 fn check_flat(bytes: &[u8], values: &[Vec<u8>]) {
1496 let flat = decode_flat(bytes).unwrap();
1497 let shape = describe(bytes).unwrap();
1498 assert_eq!(flat.len(), values.len(), "{shape}");
1499 assert_eq!(flat.iter().collect::<Vec<_>>(), borrow(values), "{shape}");
1500 assert_eq!(flat.bytes(), values.concat(), "{shape}");
1501 assert_eq!(flat.get(values.len()), None, "{shape}");
1502 }
1503
1504 fn kind_of(bytes: &[u8]) -> Kind {
1505 Kind::from_tag(bytes[0]).unwrap()
1506 }
1507
1508 #[test]
1509 fn every_shape_decodes_flat_to_what_it_decodes_split() {
1510 let columns =
1514 [urls(600), keyed(urls(600)), vec![b"same".to_vec(); 400], vec![Vec::new(); 7]];
1515 for values in &columns {
1516 let borrowed = borrow(values);
1517 for kind in offered(&borrowed) {
1518 let Some(bytes) = encode_only(kind, &borrowed).unwrap() else {
1519 continue;
1520 };
1521 assert_eq!(decode(&bytes).unwrap(), *values, "{}", kind.name());
1522 let flat = decode_flat(&bytes).unwrap();
1523 assert_eq!(flat.iter().collect::<Vec<_>>(), borrowed, "{}", kind.name());
1524 assert_eq!(flat.bytes(), values.concat(), "{}", kind.name());
1525 }
1526 }
1527 }
1528
1529 #[test]
1530 fn a_front_coded_chunk_that_shares_more_than_it_has_is_an_error() {
1531 let suffixes: [&[u8]; 2] = [b"abc", b"x"];
1535 let mut bytes = vec![Kind::Front.tag()];
1536 put_u32(&mut bytes, 2);
1537 bytes.extend_from_slice(&integer::encode(&[0, 9]).unwrap());
1538 bytes.extend_from_slice(&encode_only(Kind::Plain, &suffixes).unwrap().unwrap());
1539 let error = decode_flat(&bytes).expect_err("a nine byte prefix of a three byte value");
1540 assert_eq!(error.message(), "a value shares 9 bytes with a value 3 bytes long");
1541 assert_eq!(decode(&bytes).unwrap_err().message(), error.message());
1542 }
1543
1544 #[test]
1545 fn the_dictionary_is_sorted_and_the_codes_point_back_at_the_values() {
1546 let values = vec![
1549 b"pear".to_vec(),
1550 b"apple".to_vec(),
1551 b"pear".to_vec(),
1552 b"cherry".to_vec(),
1553 b"apple".to_vec(),
1554 ];
1555 let borrowed = borrow(&values);
1556 let (entries, codes) = dictionary_of(&borrowed);
1557 assert_eq!(entries, vec![b"apple".as_slice(), b"cherry".as_slice(), b"pear".as_slice()]);
1558 assert_eq!(codes, vec![2, 0, 2, 1, 0]);
1559 for (code, value) in codes.iter().zip(&borrowed) {
1560 assert_eq!(entries[*code as usize], *value);
1561 }
1562 }
1563
1564 #[test]
1565 fn a_column_with_nothing_repeated_has_no_duplicates_and_one_with_anything_does() {
1566 let distinct: Vec<Vec<u8>> =
1567 (0..5000).map(|index| format!("value-{index}").into_bytes()).collect();
1568 assert!(!has_duplicates(&borrow(&distinct)));
1569
1570 let mut repeated = distinct.clone();
1572 repeated.push(b"value-0".to_vec());
1573 assert!(has_duplicates(&borrow(&repeated)));
1574
1575 assert!(!has_duplicates(&borrow(&Vec::new())));
1576 assert!(!has_duplicates(&borrow(&[b"one".to_vec()])));
1577 assert!(has_duplicates(&borrow(&vec![b"same".to_vec(); 2])));
1578 }
1579
1580 #[test]
1581 fn long_values_that_differ_only_at_the_end_are_not_confused_for_each_other() {
1582 let stem = "http://www.example.com/a/very/long/path/that/goes/on?session=";
1585 let values: Vec<Vec<u8>> =
1586 (0..2000).map(|index| format!("{stem}{index}").into_bytes()).collect();
1587 assert!(!has_duplicates(&borrow(&values)));
1588 let (entries, codes) = dictionary_of(&borrow(&values));
1589 assert_eq!(entries.len(), values.len());
1590 assert_eq!(codes.len(), values.len());
1591 }
1592
1593 #[test]
1594 fn what_the_chooser_returns_is_the_smallest_of_what_it_was_offered() {
1595 for values in [urls(400), keyed(urls(400)), vec![b"same".to_vec(); 50], Vec::new()] {
1601 let borrowed = borrow(&values);
1602 let chosen = encode(&borrowed).unwrap();
1603 let mut smallest: Option<Vec<u8>> = None;
1604 for kind in offered(&borrowed) {
1605 let Some(bytes) = encode_only(kind, &borrowed).unwrap() else {
1606 continue;
1607 };
1608 if smallest.as_ref().is_none_or(|best| bytes.len() < best.len()) {
1609 smallest = Some(bytes);
1610 }
1611 }
1612 assert_eq!(smallest.as_deref(), Some(chosen.as_slice()), "{}", values.len());
1613 }
1614 }
1615
1616 fn raw_size(values: &[Vec<u8>]) -> usize {
1617 values.iter().map(Vec::len).sum::<usize>() + values.len() * 4
1618 }
1619
1620 #[test]
1621 fn a_matched_chunk_replays_literals_whether_or_not_they_are_compressed() {
1622 let compressed = describe(&round_trip(&keyed(urls(20_000)))).unwrap();
1628 assert!(compressed.starts_with("LZ(") && compressed.contains(", FSST["), "{compressed}");
1629
1630 let buffered = describe(&round_trip(&keyed(urls(300)))).unwrap();
1631 assert!(buffered.starts_with("LZ(") && buffered.contains(", PLAIN("), "{buffered}");
1632 }
1633
1634 #[test]
1635 fn a_copy_back_writes_what_a_byte_at_a_time_copy_writes_at_every_distance() {
1636 let seed: Vec<u8> = (0..40u8).map(|byte| byte.wrapping_mul(37).wrapping_add(11)).collect();
1640 for offset in 1..=seed.len() {
1641 for length in 1..=50 {
1642 let mut wanted = seed.clone();
1643 for _ in 0..length {
1644 wanted.push(wanted[wanted.len() - offset]);
1645 }
1646 let mut out = seed.clone();
1647 out.resize(seed.len() + length + REPLAY_SLACK, 0);
1648 let end = copy_back(&mut out, 0, seed.len(), offset, length).unwrap();
1649 assert_eq!(&out[..end], wanted.as_slice(), "offset {offset} length {length}");
1650 }
1651 }
1652 let mut short = vec![1, 2, 3, 0];
1653 assert!(copy_back(&mut short, 0, 3, 1, 2).is_err(), "past the end of the buffer");
1654 assert!(copy_back(&mut short, 0, 3, 4, 1).is_err(), "further back than the output");
1655 }
1656
1657 #[test]
1658 fn an_empty_chunk_round_trips() {
1659 let bytes = round_trip(&[]);
1660 assert_eq!(kind_of(&bytes), Kind::Plain);
1661 }
1662
1663 #[test]
1664 fn a_constant_column_costs_what_one_value_costs() {
1665 let values = vec![b"https://www.example.com/".to_vec(); 100_000];
1666 let bytes = round_trip(&values);
1667 assert_eq!(kind_of(&bytes), Kind::Constant);
1668 assert_eq!(bytes.len(), 9 + 24);
1669 }
1670
1671 #[test]
1672 fn a_url_column_of_unique_values_is_matched_rather_than_only_compressed() {
1673 let values = keyed(urls(20_000));
1681 let bytes = round_trip(&values);
1682 assert_eq!(kind_of(&bytes), Kind::Lz);
1683
1684 let borrowed: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
1687 let fsst = encode_as(Kind::Fsst, &borrowed, 0, &EXHAUSTIVE).unwrap().unwrap();
1688 assert!(bytes.len() < fsst.len(), "{} against FSST {}", bytes.len(), fsst.len());
1689
1690 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1693 assert!(ratio > 4.0, "{ratio:.2}x");
1694 }
1695
1696 #[test]
1697 fn a_sample_of_a_periodic_column_learns_every_phase_of_it() {
1698 let values = urls(20_000);
1703 let borrowed = borrow(&values);
1704 let sample = sample_of(&borrowed);
1705 let mut phases: Vec<&[u8]> = sample
1706 .iter()
1707 .map(|value| {
1708 let query =
1709 value.iter().position(|byte| *byte == b'?').expect("every value has a query");
1710 &value[..query]
1711 })
1712 .collect();
1713 phases.sort_unstable();
1714 phases.dedup();
1715 assert_eq!(phases.len(), 12);
1717 let whole = SymbolTable::train(&borrowed);
1718 let sampled = SymbolTable::train(&sample);
1719 let mut on_whole = Vec::new();
1720 let mut on_sample = Vec::new();
1721 for value in &borrowed {
1722 whole.compress(value, &mut on_whole);
1723 sampled.compress(value, &mut on_sample);
1724 }
1725 assert!(
1728 on_sample.len() < on_whole.len() * 5 / 4,
1729 "{} against {}",
1730 on_sample.len(),
1731 on_whole.len()
1732 );
1733 }
1734
1735 #[test]
1736 fn a_repeating_column_becomes_a_dictionary_of_compressed_entries() {
1737 let distinct = urls(500);
1747 let values: Vec<Vec<u8>> = (0..50_000_u64)
1748 .map(|index| {
1749 let hashed = (index.wrapping_mul(0x9E37_79B9_7F4A_7C15) >> 32) as usize;
1750 distinct[hashed % distinct.len()].clone()
1751 })
1752 .collect();
1753 let bytes = round_trip(&values);
1754 assert_eq!(kind_of(&bytes), Kind::Dict);
1755 let shape = describe(&bytes).unwrap();
1756 assert!(shape.starts_with("DICT(LZ("), "{shape}");
1757 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1758 assert!(ratio > 20.0, "{ratio:.2}x, {shape}");
1759 }
1760
1761 #[test]
1762 fn a_column_of_long_runs_costs_almost_nothing() {
1763 let distinct = urls(50);
1766 let mut values = Vec::new();
1767 for entry in &distinct {
1768 values.extend(std::iter::repeat_n(entry.clone(), 1000));
1769 }
1770 let bytes = round_trip(&values);
1771 let shape = describe(&bytes).unwrap();
1772 assert!(shape.contains("RLE"), "{shape}");
1773 assert!(bytes.len() < 2000, "{} bytes: {shape}", bytes.len());
1774 }
1775
1776 #[test]
1777 fn incompressible_strings_stay_close_to_their_own_size() {
1778 let mut state = 0x2545_f491_4f6c_dd1du64;
1781 let values: Vec<Vec<u8>> = (0..2000)
1782 .map(|_| {
1783 (0..32)
1784 .map(|_| {
1785 state ^= state << 13;
1786 state ^= state >> 7;
1787 state ^= state << 17;
1788 state as u8
1789 })
1790 .collect()
1791 })
1792 .collect();
1793 let bytes = round_trip(&values);
1794 assert!(bytes.len() < 2000 * 32 + 3000, "{} bytes", bytes.len());
1795 }
1796
1797 #[test]
1798 fn lengths_are_stored_rather_than_offsets() {
1799 let values: Vec<Vec<u8>> =
1802 (0..100_000).map(|index| format!("{index:024}").into_bytes()).collect();
1803 let borrowed = borrow(&values);
1804 let bytes = encode_only(Kind::Plain, &borrowed).unwrap().unwrap();
1805 assert_eq!(bytes.len(), 5 + 13 + 100_000 * 24);
1806 }
1807
1808 #[test]
1809 fn empty_strings_are_values_and_not_nulls() {
1810 let values = vec![Vec::new(), b"a".to_vec(), Vec::new(), b"bb".to_vec()];
1811 round_trip(&values);
1812 }
1813
1814 #[test]
1815 fn a_chunk_with_one_value_round_trips() {
1816 round_trip(&[b"only".to_vec()]);
1817 }
1818
1819 #[test]
1820 fn every_candidate_that_applies_decodes_to_the_input() {
1821 let values = urls(3000);
1822 let borrowed = borrow(&values);
1823 let applicable = candidates(&borrowed, 0);
1824 assert!(applicable.len() >= 2, "{applicable:?}");
1825 for kind in applicable {
1826 let bytes = encode_only(kind, &borrowed).unwrap().unwrap();
1827 assert_eq!(decode(&bytes).unwrap(), values, "{}", kind.name());
1828 }
1829 }
1830
1831 #[test]
1832 fn the_chooser_picks_the_smallest_candidate() {
1833 let values = urls(2000);
1834 let borrowed = borrow(&values);
1835 let chosen = encode(&borrowed).unwrap();
1836 for (_, size) in candidate_sizes(&borrowed).unwrap() {
1837 assert!(chosen.len() <= size);
1838 }
1839 }
1840
1841 #[test]
1842 fn a_truncated_chunk_is_an_error_and_not_a_panic() {
1843 let values = urls(40);
1844 let bytes = encode(&borrow(&values)).unwrap();
1845 for len in 0..bytes.len() {
1846 assert!(decode(&bytes[..len]).is_err(), "{len} bytes decoded");
1847 }
1848 }
1849
1850 #[test]
1851 fn trailing_bytes_are_an_error() {
1852 let mut bytes = encode(&borrow(&urls(10))).unwrap();
1853 bytes.push(0);
1854 let error = decode(&bytes).unwrap_err();
1855 assert!(error.message().contains("left over"), "{error}");
1856 }
1857
1858 #[test]
1859 fn an_unknown_tag_is_an_error() {
1860 let error = decode(&[99, 0, 0, 0, 0]).unwrap_err();
1861 assert!(error.message().contains("unknown string encoding tag"), "{error}");
1862 }
1863
1864 #[test]
1865 fn a_dictionary_code_outside_the_dictionary_is_an_error() {
1866 let mut bytes = vec![Kind::Dict.tag()];
1867 put_u32(&mut bytes, 1);
1868 bytes.extend_from_slice(&encode(&[b"one".as_slice()]).unwrap());
1869 bytes.extend_from_slice(&integer::encode(&[9]).unwrap());
1870 let error = decode(&bytes).unwrap_err();
1871 assert!(error.message().contains("not in the dictionary"), "{error}");
1872 }
1873
1874 #[test]
1875 fn a_sorted_column_of_urls_is_front_coded() {
1876 let mut values = urls(20_000);
1880 values.sort();
1881 let bytes = round_trip(&values);
1882 assert_eq!(kind_of(&bytes), Kind::Front);
1883 let shape = describe(&bytes).unwrap();
1884 let mut plain = Vec::new();
1885 let borrowed = borrow(&values);
1886 for (kind, size) in candidate_sizes(&borrowed).unwrap() {
1887 if kind == Kind::Fsst {
1888 plain.push(size);
1889 }
1890 }
1891 let fsst = plain[0];
1892 assert!(bytes.len() * 2 < fsst, "{} against FSST {fsst}: {shape}", bytes.len());
1893 }
1894
1895 #[test]
1896 fn a_column_with_nothing_to_share_is_not_offered_front_coding() {
1897 let mut state = 0x9e37_79b9_7f4a_7c15u64;
1900 let values: Vec<Vec<u8>> = (0..2000)
1901 .map(|_| {
1902 (0..24)
1903 .map(|_| {
1904 state ^= state << 13;
1905 state ^= state >> 7;
1906 state ^= state << 17;
1907 (state % 251) as u8
1908 })
1909 .collect()
1910 })
1911 .collect();
1912 let borrowed = borrow(&values);
1913 assert!(!candidates(&borrowed, 0).contains(&Kind::Front));
1914 }
1915
1916 #[test]
1917 fn a_prefix_longer_than_the_value_before_it_is_an_error() {
1918 let mut bytes = vec![Kind::Front.tag()];
1919 put_u32(&mut bytes, 2);
1920 bytes.extend_from_slice(&integer::encode(&[0, 9]).unwrap());
1921 bytes.extend_from_slice(&encode(&[b"one".as_slice(), b"two".as_slice()]).unwrap());
1922 let error = decode(&bytes).unwrap_err();
1923 assert!(error.message().contains("shares 9 bytes"), "{error}");
1924 }
1925
1926 #[test]
1927 fn a_negative_prefix_is_an_error() {
1928 let mut bytes = vec![Kind::Front.tag()];
1929 put_u32(&mut bytes, 1);
1930 bytes.extend_from_slice(&integer::encode(&[-1]).unwrap());
1931 bytes.extend_from_slice(&encode(&[b"one".as_slice()]).unwrap());
1932 let error = decode(&bytes).unwrap_err();
1933 assert!(error.message().contains("negative shared prefix"), "{error}");
1934 }
1935
1936 #[test]
1937 fn a_negative_length_is_an_error() {
1938 let mut bytes = vec![Kind::Plain.tag()];
1939 put_u32(&mut bytes, 1);
1940 bytes.extend_from_slice(&integer::encode(&[-1]).unwrap());
1941 let error = decode(&bytes).unwrap_err();
1942 assert!(error.message().contains("negative string length"), "{error}");
1943 }
1944
1945 #[test]
1946 fn the_sample_is_spread_across_the_chunk_and_not_taken_from_the_front() {
1947 let mut values: Vec<Vec<u8>> = Vec::new();
1950 for index in 0..20_000 {
1951 let head = if index < 10_000 { "aaaaaaaaaaaaaaaa" } else { "zzzzzzzzzzzzzzzz" };
1952 values.push(format!("{head}/{index:08}").into_bytes());
1953 }
1954 let borrowed = borrow(&values);
1955 let sample = sample_of(&borrowed);
1956 let first_half = sample.iter().filter(|value| value.starts_with(b"aaaa")).count();
1957 let second_half = sample.len() - first_half;
1958 assert!(first_half > 0 && second_half > 0, "{first_half} and {second_half}");
1959 let bytes = round_trip(&values);
1960 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1961 assert!(ratio > 4.0, "{ratio:.2}x");
1962 }
1963}