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::tally::{self, Family};
74
75const MAX_DEPTH: u8 = 2;
78
79const SHARE_DIVISOR: usize = 20;
83
84const LZ_FLOOR: usize = 4096;
90
91pub(crate) const SAMPLE_BYTES: usize = 64 * 1024;
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum Kind {
101 Constant = 0,
103 Plain = 1,
105 Fsst = 2,
107 Dict = 3,
109 Front = 4,
111 Lz = 5,
114}
115
116impl Kind {
117 pub const ALL: [Self; 6] =
119 [Self::Constant, Self::Plain, Self::Fsst, Self::Dict, Self::Front, Self::Lz];
120
121 fn tag(self) -> u8 {
122 self as u8
123 }
124
125 fn from_tag(tag: u8) -> Result<Self> {
126 match tag {
127 0 => Ok(Self::Constant),
128 1 => Ok(Self::Plain),
129 2 => Ok(Self::Fsst),
130 3 => Ok(Self::Dict),
131 4 => Ok(Self::Front),
132 5 => Ok(Self::Lz),
133 other => Err(Error::internal(format!("unknown string encoding tag {other}"))),
134 }
135 }
136
137 #[must_use]
139 pub fn name(self) -> &'static str {
140 match self {
141 Self::Constant => "CONSTANT",
142 Self::Plain => "PLAIN",
143 Self::Fsst => "FSST",
144 Self::Dict => "DICT",
145 Self::Front => "FRONT",
146 Self::Lz => "LZ",
147 }
148 }
149}
150
151pub fn encode(values: &[&[u8]]) -> Result<Vec<u8>> {
162 encode_with(values, &EXHAUSTIVE)
163}
164
165pub fn encode_with(values: &[&[u8]], chooser: &dyn Chooser) -> Result<Vec<u8>> {
175 encode_at(values, 0, chooser)
176}
177
178#[derive(Debug, Clone, Default, PartialEq, Eq)]
191pub struct Flat {
192 bytes: Vec<u8>,
193 ends: Vec<usize>,
197}
198
199impl Flat {
200 fn with_capacity(count: usize, bytes: usize) -> Self {
201 Self { bytes: Vec::with_capacity(bytes), ends: Vec::with_capacity(count) }
202 }
203
204 fn push(&mut self, value: &[u8]) {
205 self.bytes.extend_from_slice(value);
206 self.ends.push(self.bytes.len());
207 }
208
209 fn start(&self, index: usize) -> usize {
211 if index == 0 { 0 } else { self.ends[index - 1] }
212 }
213
214 #[must_use]
216 pub fn len(&self) -> usize {
217 self.ends.len()
218 }
219
220 #[must_use]
222 pub fn is_empty(&self) -> bool {
223 self.ends.is_empty()
224 }
225
226 #[must_use]
229 pub fn bytes(&self) -> &[u8] {
230 &self.bytes
231 }
232
233 #[must_use]
235 pub fn get(&self, index: usize) -> Option<&[u8]> {
236 let end = *self.ends.get(index)?;
237 self.bytes.get(self.start(index)..end)
238 }
239
240 pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
242 let mut at = 0;
243 self.ends.iter().map(move |end| {
244 let value = self.bytes.get(at..*end).unwrap_or_default();
245 at = *end;
246 value
247 })
248 }
249
250 #[must_use]
252 pub fn into_bytes(self) -> Vec<u8> {
253 self.bytes
254 }
255
256 #[must_use]
263 pub fn into_parts(self) -> (Vec<u8>, Vec<usize>) {
264 (self.bytes, self.ends)
265 }
266
267 fn into_values(self) -> Vec<Vec<u8>> {
268 let mut values = Vec::with_capacity(self.len());
269 let mut at = 0;
270 for end in &self.ends {
271 values.push(self.bytes[at..*end].to_vec());
272 at = *end;
273 }
274 values
275 }
276}
277
278pub fn decode_flat(bytes: &[u8]) -> Result<Flat> {
284 let mut reader = Reader::new(bytes);
285 let flat = decode_chunk(&mut reader)?;
286 if reader.remaining() != 0 {
287 return Err(Error::internal(format!(
288 "{} bytes left over after decoding a string chunk",
289 reader.remaining()
290 )));
291 }
292 Ok(flat)
293}
294
295pub fn decode_flat_at(bytes: &[u8], positions: &[u32]) -> Result<Flat> {
307 if positions.windows(2).any(|pair| pair[0] >= pair[1]) {
308 return Err(Error::internal("the positions to decode do not rise"));
309 }
310 let mut reader = Reader::new(bytes);
311 let flat = if bytes.first() == Some(&Kind::Fsst.tag()) {
312 reader.u8()?;
313 let count = reader.u32()? as usize;
314 let runs = read_compressed(&mut reader, count)?;
315 let mut flat = Flat::with_capacity(positions.len(), runs.payload.len());
316 let mut at = 0;
317 let mut next = 0;
318 for &position in positions {
319 let position = position as usize;
320 if position >= count {
321 return Err(Error::internal(format!("value {position} is not in the chunk")));
322 }
323 at += runs.lengths[next..position].iter().sum::<usize>();
324 runs.run_into(position, &mut at, &mut flat.bytes)?;
325 flat.ends.push(flat.bytes.len());
326 next = position + 1;
327 }
328 flat
329 } else {
330 let whole = decode_chunk(&mut reader)?;
331 let mut flat = Flat::with_capacity(positions.len(), 0);
332 for &position in positions {
333 let value = whole
334 .get(position as usize)
335 .ok_or_else(|| Error::internal(format!("value {position} is not in the chunk")))?;
336 flat.push(value);
337 }
338 flat
339 };
340 if reader.remaining() != 0 {
341 return Err(Error::internal(format!(
342 "{} bytes left over after decoding a string chunk",
343 reader.remaining()
344 )));
345 }
346 Ok(flat)
347}
348
349pub fn decode_prefix(bytes: &[u8]) -> Result<(Vec<Vec<u8>>, usize)> {
358 let mut reader = Reader::new(bytes);
359 let values = decode_chunk(&mut reader)?;
360 Ok((values.into_values(), reader.used()))
361}
362
363pub fn describe_prefix(bytes: &[u8]) -> Result<(String, usize)> {
369 let mut reader = Reader::new(bytes);
370 let text = describe_chunk(&mut reader)?;
371 Ok((text, reader.used()))
372}
373
374pub fn decode(bytes: &[u8]) -> Result<Vec<Vec<u8>>> {
380 Ok(decode_flat(bytes)?.into_values())
381}
382
383pub fn candidate_sizes(values: &[&[u8]]) -> Result<Vec<(Kind, usize)>> {
390 let mut sizes = Vec::new();
391 for kind in candidates(values, 0) {
392 if let Some(bytes) = encode_as(kind, values, 0, &EXHAUSTIVE)? {
393 sizes.push((kind, bytes.len()));
394 }
395 }
396 Ok(sizes)
397}
398
399#[must_use]
406pub fn offered(values: &[&[u8]]) -> Vec<Kind> {
407 candidates(values, 0)
408}
409
410pub fn encode_only(kind: Kind, values: &[&[u8]]) -> Result<Option<Vec<u8>>> {
422 encode_as(kind, values, 0, &EXHAUSTIVE)
423}
424
425#[must_use]
435pub fn fsst_shape(values: &[&[u8]]) -> Settled {
436 let started = Instant::now();
437 let table = SymbolTable::train(&sample_of(values));
438 tally::chose(Family::String, started);
439 Settled::new(vec![Kind::Fsst], Vec::new()).with_symbols(0, table)
440}
441
442pub fn encode_fsst(values: &[&[u8]], shape: &Settled) -> Result<Option<Vec<u8>>> {
452 let out =
453 tally::offer(Family::String, Kind::Fsst.tag(), || encode_as(Kind::Fsst, values, 0, shape))?;
454 if out.is_some() {
455 tally::kept(Family::String, Kind::Fsst.tag());
456 }
457 Ok(out)
458}
459
460pub(crate) fn size_as(kind: Kind, values: &[&[u8]], depth: u8) -> Result<Option<usize>> {
465 Ok(encode_as(kind, values, depth, &EXHAUSTIVE)?.map(|bytes| bytes.len()))
466}
467
468pub fn describe(bytes: &[u8]) -> Result<String> {
474 let mut reader = Reader::new(bytes);
475 describe_chunk(&mut reader)
476}
477
478#[must_use]
490pub fn with_symbols(shape: Settled, blocks: &[Vec<&[u8]>]) -> Settled {
491 let kinds = shape.strings();
492 let Some(depth) =
493 (0..=kinds.len()).find(|&at| matches!(kinds.get(at), Some(Kind::Fsst) | None))
494 else {
495 return shape;
496 };
497 let leads =
498 kinds[..depth].iter().all(|kind| matches!(kind, Kind::Front | Kind::Lz | Kind::Dict));
499 if !leads || depth > usize::from(MAX_DEPTH) {
500 return shape;
501 }
502 let mut reached: Vec<Vec<u8>> = Vec::new();
503 for block in blocks {
504 let mut values: Vec<Vec<u8>> = block.iter().map(|value| value.to_vec()).collect();
505 for kind in &kinds[..depth] {
506 let refs: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
507 values = match kind {
508 Kind::Front => front_code(&refs).1.into_iter().map(<[u8]>::to_vec).collect(),
509 Kind::Dict => dictionary_of(&refs).0.into_iter().map(<[u8]>::to_vec).collect(),
510 _ => {
511 let joined = refs.concat();
512 lz::tokens_of(&joined).literals.into_iter().map(<[u8]>::to_vec).collect()
513 }
514 };
515 }
516 reached.extend(values);
517 }
518 let refs: Vec<&[u8]> = reached.iter().map(Vec::as_slice).collect();
519 let table = SymbolTable::train(&sample_of(&refs));
520 if table.is_empty() {
521 return shape;
522 }
523 shape.with_symbols(depth as u8, table)
524}
525
526fn encode_at(values: &[&[u8]], depth: u8, chooser: &dyn Chooser) -> Result<Vec<u8>> {
527 let started = Instant::now();
528 let offered = candidates(values, depth);
529 let narrowed = chooser.narrow_strings(values, &offered, depth);
530 let counted = depth == 0;
532 if counted {
533 tally::chose(Family::String, started);
534 }
535 let mut best: Option<(Kind, Vec<u8>)> = None;
536 for kind in narrowed {
537 let encoded = if counted {
538 tally::offer(Family::String, kind.tag(), || encode_as(kind, values, depth, chooser))?
539 } else {
540 encode_as(kind, values, depth, chooser)?
541 };
542 let Some(bytes) = encoded else {
543 continue;
544 };
545 if best.as_ref().is_none_or(|(_, current)| bytes.len() < current.len()) {
546 best = Some((kind, bytes));
547 }
548 }
549 let (kind, bytes) =
550 best.ok_or_else(|| Error::internal("no string encoding applied to the chunk"))?;
551 if counted {
552 tally::kept(Family::String, kind.tag());
553 }
554 Ok(bytes)
555}
556
557fn candidates(values: &[&[u8]], depth: u8) -> Vec<Kind> {
558 let mut kinds = vec![Kind::Plain];
559 if values.is_empty() {
560 return kinds;
561 }
562 if values.iter().all(|value| *value == values[0]) {
563 return vec![Kind::Constant];
564 }
565 kinds.push(Kind::Fsst);
566 if depth < MAX_DEPTH && has_duplicates(values) {
567 kinds.push(Kind::Dict);
568 }
569 if depth < MAX_DEPTH && sharing_of(values) >= total_len(values) / SHARE_DIVISOR {
570 kinds.push(Kind::Front);
571 }
572 if depth < MAX_DEPTH && total_len(values) >= LZ_FLOOR {
573 kinds.push(Kind::Lz);
574 }
575 kinds
576}
577
578fn sharing_of(values: &[&[u8]]) -> usize {
585 let mut shared = 0;
586 for pair in values.windows(2) {
587 shared += shared_prefix(pair[0], pair[1]);
588 }
589 shared
590}
591
592pub(crate) fn front_code<'a>(values: &[&'a [u8]]) -> (Vec<i64>, Vec<&'a [u8]>) {
598 let mut prefixes = Vec::with_capacity(values.len());
599 let mut suffixes: Vec<&'a [u8]> = Vec::with_capacity(values.len());
600 let mut previous: &[u8] = b"";
601 for value in values {
602 let value: &'a [u8] = value;
603 let shared = shared_prefix(previous, value);
604 prefixes.push(shared as i64);
605 suffixes.push(&value[shared..]);
606 previous = value;
607 }
608 (prefixes, suffixes)
609}
610
611pub(crate) fn front_decode(prefixes: &[i64], suffixes: Vec<Vec<u8>>) -> Result<Vec<Vec<u8>>> {
618 let mut values: Vec<Vec<u8>> = Vec::with_capacity(suffixes.len());
619 for (index, suffix) in suffixes.into_iter().enumerate() {
620 let shared = usize::try_from(prefixes[index])
621 .map_err(|_| Error::internal("a negative shared prefix length"))?;
622 let previous: &[u8] = if index == 0 { b"" } else { &values[index - 1] };
623 if shared > previous.len() {
624 return Err(Error::internal(format!(
625 "a value shares {shared} bytes with a value {} bytes long",
626 previous.len()
627 )));
628 }
629 let mut value = Vec::with_capacity(shared + suffix.len());
630 value.extend_from_slice(&previous[..shared]);
631 value.extend_from_slice(&suffix);
632 values.push(value);
633 }
634 Ok(values)
635}
636
637fn shared_prefix(previous: &[u8], value: &[u8]) -> usize {
638 let limit = previous.len().min(value.len());
639 let mut shared = 0;
640 while shared < limit && previous[shared] == value[shared] {
641 shared += 1;
642 }
643 shared
644}
645
646fn total_len(values: &[&[u8]]) -> usize {
647 values.iter().map(|value| value.len()).sum()
648}
649
650fn encode_as(
651 kind: Kind,
652 values: &[&[u8]],
653 depth: u8,
654 chooser: &dyn Chooser,
655) -> Result<Option<Vec<u8>>> {
656 let mut out = vec![kind.tag()];
657 put_u32(&mut out, u32::try_from(values.len()).map_err(|_| too_long(values.len()))?);
658 match kind {
659 Kind::Constant => {
660 let Some(first) = values.first() else {
661 return Ok(None);
662 };
663 if values.iter().any(|value| value != first) {
664 return Ok(None);
665 }
666 put_u32(&mut out, u32::try_from(first.len()).map_err(|_| too_long(first.len()))?);
667 out.extend_from_slice(first);
668 }
669 Kind::Plain => {
670 out.extend_from_slice(&encode_lengths(values, chooser)?);
671 for value in values {
672 out.extend_from_slice(value);
673 }
674 }
675 Kind::Fsst => {
676 let trained;
677 let table = match chooser.symbols(depth) {
678 Some(table) => table,
679 None => {
680 trained = SymbolTable::train(&sample_of(values));
681 &trained
682 }
683 };
684 if table.is_empty() {
685 return Ok(None);
686 }
687 let mut compressed = Vec::new();
688 let mut lengths = Vec::with_capacity(values.len());
689 for value in values {
690 let before = compressed.len();
691 table.compress(value, &mut compressed);
692 lengths.push((compressed.len() - before) as i64);
693 }
694 table.serialize(&mut out);
695 out.extend_from_slice(&integer::encode_with(&lengths, chooser)?);
696 out.extend_from_slice(&compressed);
697 }
698 Kind::Dict => {
699 let (entries, codes) = dictionary_of(values);
700 if entries.is_empty() {
701 return Ok(None);
702 }
703 out.extend_from_slice(&encode_at(&entries, depth + 1, chooser)?);
704 out.extend_from_slice(&integer::encode_with(&codes, chooser)?);
705 }
706 Kind::Front => {
707 let (prefixes, suffixes) = front_code(values);
708 out.extend_from_slice(&integer::encode_with(&prefixes, chooser)?);
709 out.extend_from_slice(&encode_at(&suffixes, depth + 1, chooser)?);
710 }
711 Kind::Lz => {
712 let mut joined = Vec::with_capacity(total_len(values));
713 let mut sizes = Vec::with_capacity(values.len());
714 for value in values {
715 joined.extend_from_slice(value);
716 sizes.push(value.len() as i64);
717 }
718 let tokens = lz::tokens_of(&joined);
719 out.extend_from_slice(&integer::encode_with(&sizes, chooser)?);
720 out.extend_from_slice(&integer::encode_with(&tokens.lengths, chooser)?);
721 out.extend_from_slice(&integer::encode_with(&tokens.offsets, chooser)?);
722 out.extend_from_slice(&encode_at(&tokens.literals, depth + 1, chooser)?);
723 }
724 }
725 Ok(Some(out))
726}
727
728fn decode_chunk(reader: &mut Reader<'_>) -> Result<Flat> {
729 let kind = Kind::from_tag(reader.u8()?)?;
730 let count = reader.u32()? as usize;
731 match kind {
732 Kind::Constant => {
733 let len = reader.u32()? as usize;
734 let value = reader.bytes(len)?;
735 let mut flat = Flat::with_capacity(count, len.saturating_mul(count));
736 for _ in 0..count {
737 flat.push(value);
738 }
739 Ok(flat)
740 }
741 Kind::Plain => {
742 let lengths = decode_lengths(reader, count)?;
743 let total = sum_of(&lengths)?;
746 let payload = reader.bytes(total)?;
747 let mut flat = Flat::with_capacity(count, total);
748 flat.bytes.extend_from_slice(payload);
749 let mut at = 0;
750 for length in lengths {
751 at += length;
752 flat.ends.push(at);
753 }
754 Ok(flat)
755 }
756 Kind::Fsst => {
757 let runs = read_compressed(reader, count)?;
758 let mut flat = Flat::with_capacity(count, 0);
759 runs.all_into(&mut flat)?;
760 Ok(flat)
761 }
762 Kind::Dict => {
763 let dictionary = decode_chunk(reader)?;
764 let codes = decode_integers(reader)?;
765 if codes.len() != count {
766 return Err(Error::internal(format!(
767 "a dictionary chunk says it holds {count} values and has {} codes",
768 codes.len()
769 )));
770 }
771 let mut flat = Flat::with_capacity(count, dictionary.bytes.len());
772 for code in codes {
773 let entry =
774 usize::try_from(code).ok().and_then(|index| dictionary.get(index)).ok_or_else(
775 || Error::internal(format!("code {code} is not in the dictionary")),
776 )?;
777 flat.push(entry);
778 }
779 Ok(flat)
780 }
781 Kind::Front => {
782 let prefixes = decode_integers(reader)?;
783 let suffixes = decode_chunk(reader)?;
784 if prefixes.len() != count || suffixes.len() != count {
785 return Err(Error::internal(format!(
786 "a front coded chunk says it holds {count} values and has {} prefixes and {} suffixes",
787 prefixes.len(),
788 suffixes.len()
789 )));
790 }
791 let mut room = 0usize;
798 let mut previous = 0usize;
799 for (index, prefix) in prefixes.iter().enumerate() {
800 let shared = usize::try_from(*prefix)
801 .map_err(|_| Error::internal("a negative shared prefix length"))?;
802 if shared > previous {
803 return Err(Error::internal(format!(
804 "a value shares {shared} bytes with a value {previous} bytes long"
805 )));
806 }
807 previous = shared + suffixes.get(index).map_or(0, <[u8]>::len);
808 room = room
809 .checked_add(previous)
810 .ok_or_else(|| Error::internal("a string chunk longer than memory"))?;
811 }
812 let mut flat = Flat::with_capacity(count, room);
813 for (index, &prefix) in prefixes.iter().enumerate() {
814 let shared = prefix as usize;
815 let from = if index == 0 { 0 } else { flat.start(index - 1) };
816 flat.bytes.extend_from_within(from..from + shared);
817 flat.bytes.extend_from_slice(suffixes.get(index).expect("in range"));
818 flat.ends.push(flat.bytes.len());
819 }
820 Ok(flat)
821 }
822 Kind::Lz => {
823 let sizes = decode_integers(reader)?;
824 let lengths = decode_integers(reader)?;
825 let offsets = decode_integers(reader)?;
826 if sizes.len() != count {
827 return Err(Error::internal(format!(
828 "a matched chunk says it holds {count} values and has {} lengths",
829 sizes.len()
830 )));
831 }
832 let mut total = 0usize;
833 let mut widths = Vec::with_capacity(count);
834 for size in sizes {
835 let width = usize::try_from(size)
836 .map_err(|_| Error::internal("a negative string length"))?;
837 total = total
838 .checked_add(width)
839 .ok_or_else(|| Error::internal("a string chunk longer than memory"))?;
840 widths.push(width);
841 }
842 let mut flat = Flat::with_capacity(count, total.saturating_add(REPLAY_SLACK));
847 replay_literals(reader, &lengths, &offsets, total, &mut flat.bytes)?;
848 if flat.bytes.len() != total {
849 return Err(Error::internal(format!(
850 "a matched chunk rebuilt {} bytes where its lengths add up to {total}",
851 flat.bytes.len()
852 )));
853 }
854 let mut at = 0;
855 for width in widths {
856 at += width;
857 flat.ends.push(at);
858 }
859 Ok(flat)
860 }
861 }
862}
863
864struct Compressed<'a> {
870 table: SymbolTable,
872 lengths: Vec<usize>,
874 payload: &'a [u8],
876}
877
878impl Compressed<'_> {
879 fn run_into(&self, index: usize, at: &mut usize, out: &mut Vec<u8>) -> Result<()> {
888 self.table.decompress(self.run(index, at)?, out)
889 }
890
891 fn all_into(&self, flat: &mut Flat) -> Result<()> {
904 let out = &mut flat.bytes;
905 let mut at = out.len();
906 let mut payload = self.payload;
907 for &run in &self.lengths {
908 let Some((codes, rest)) = payload.split_at_checked(run) else {
909 return Err(Error::internal("a compressed run is past the end of its chunk"));
910 };
911 payload = rest;
912 let need = run
913 .checked_mul(MAX_SYMBOL_LEN)
914 .and_then(|room| room.checked_add(at))
915 .ok_or_else(|| Error::internal("a compressed chunk longer than memory"))?;
916 if out.len() < need {
917 out.resize(need.max(out.len() * 2), 0);
918 }
919 at = self.table.decompress_at(codes, out, at)?;
920 flat.ends.push(at);
921 }
922 out.truncate(at);
923 Ok(())
924 }
925
926 fn run(&self, index: usize, at: &mut usize) -> Result<&[u8]> {
929 let length = *self
930 .lengths
931 .get(index)
932 .ok_or_else(|| Error::internal(format!("run {index} is not in the chunk")))?;
933 let end = at
934 .checked_add(length)
935 .ok_or_else(|| Error::internal("a compressed chunk longer than memory"))?;
936 let run = self
937 .payload
938 .get(*at..end)
939 .ok_or_else(|| Error::internal("a compressed run is past the end of its chunk"))?;
940 *at = end;
941 Ok(run)
942 }
943}
944
945fn read_compressed<'a>(reader: &mut Reader<'a>, count: usize) -> Result<Compressed<'a>> {
954 let (table, used) = SymbolTable::deserialize(reader.rest())?;
955 reader.skip(used)?;
956 let lengths = decode_lengths(reader, count)?;
957 let compressed_len = sum_of(&lengths)?;
960 if compressed_len > reader.remaining() {
961 return Err(Error::internal(format!(
962 "a compressed chunk says it holds {compressed_len} bytes and has {}",
963 reader.remaining()
964 )));
965 }
966 let payload = reader.bytes(compressed_len)?;
967 Ok(Compressed { table, lengths, payload })
968}
969
970fn replay_literals(
983 reader: &mut Reader<'_>,
984 lengths: &[i64],
985 offsets: &[i64],
986 total: usize,
987 out: &mut Vec<u8>,
988) -> Result<()> {
989 if reader.rest().first() == Some(&Kind::Fsst.tag()) {
990 reader.u8()?;
991 let runs = reader.u32()? as usize;
992 let compressed = read_compressed(reader, runs)?;
993 return replay_in_place(&compressed, lengths, offsets, total, out);
994 }
995 let literals = decode_chunk(reader)?;
996 lz::rebuild_into(&literals, lengths, offsets, out)
997}
998
999const REPLAY_SLACK: usize = 16;
1005
1006fn replay_in_place(
1018 compressed: &Compressed<'_>,
1019 lengths: &[i64],
1020 offsets: &[i64],
1021 total: usize,
1022 out: &mut Vec<u8>,
1023) -> Result<()> {
1024 let runs = compressed.lengths.len();
1025 if runs != lengths.len() || lengths.len() != offsets.len() {
1026 return Err(Error::internal(format!(
1027 "a matched chunk has {runs} literal runs, {} lengths and {} offsets",
1028 lengths.len(),
1029 offsets.len()
1030 )));
1031 }
1032 let base = out.len();
1033 let room = total
1034 .checked_add(REPLAY_SLACK)
1035 .ok_or_else(|| Error::internal("a string chunk longer than memory"))?;
1036 out.resize(base + room, 0);
1037 let mut payload = compressed.payload;
1038 let mut at = base;
1039 for ((&run, &length), &offset) in compressed.lengths.iter().zip(lengths).zip(offsets) {
1040 let Some((codes, rest)) = payload.split_at_checked(run) else {
1041 return Err(Error::internal("a compressed run is past the end of its chunk"));
1042 };
1043 payload = rest;
1044 at = compressed.table.decompress_at(codes, out, at)?;
1045 let length =
1046 usize::try_from(length).map_err(|_| Error::internal("a negative copy length"))?;
1047 if length == 0 {
1048 continue;
1049 }
1050 let offset =
1051 usize::try_from(offset).map_err(|_| Error::internal("a negative copy offset"))?;
1052 at = copy_back(out, base, at, offset, length)?;
1053 }
1054 if at > base + total {
1055 return Err(Error::internal(format!(
1056 "a matched chunk rebuilt {} bytes where its lengths add up to {total}",
1057 at - base
1058 )));
1059 }
1060 out.truncate(at);
1061 Ok(())
1062}
1063
1064fn copy_back(
1072 out: &mut [u8],
1073 base: usize,
1074 at: usize,
1075 offset: usize,
1076 length: usize,
1077) -> Result<usize> {
1078 if offset == 0 || offset > at - base {
1079 return Err(Error::internal(format!(
1080 "a copy reaches {offset} bytes back into {} bytes of output",
1081 at - base
1082 )));
1083 }
1084 let end = at
1085 .checked_add(length)
1086 .filter(|&end| end <= out.len())
1087 .ok_or_else(|| Error::internal("a matched chunk rebuilds more than its lengths say"))?;
1088 let from = at - offset;
1089 let wide = end + REPLAY_SLACK <= out.len();
1090 if wide && offset >= 16 {
1091 let mut step = 0;
1092 while step < length {
1093 out.copy_within(from + step..from + step + 16, at + step);
1094 step += 16;
1095 }
1096 } else if wide && offset >= 8 {
1097 let mut step = 0;
1098 while step < length {
1099 out.copy_within(from + step..from + step + 8, at + step);
1100 step += 8;
1101 }
1102 } else {
1103 for step in 0..length {
1104 out[at + step] = out[from + step];
1105 }
1106 }
1107 Ok(end)
1108}
1109
1110fn describe_chunk(reader: &mut Reader<'_>) -> Result<String> {
1111 let kind = Kind::from_tag(reader.u8()?)?;
1112 let count = reader.u32()? as usize;
1113 Ok(match kind {
1114 Kind::Constant => {
1115 let len = reader.u32()? as usize;
1116 reader.bytes(len)?;
1117 "CONSTANT".to_string()
1118 }
1119 Kind::Plain => {
1120 let (shape, lengths) = describe_lengths(reader, count)?;
1121 reader.skip(lengths.iter().sum())?;
1122 format!("PLAIN({shape})")
1123 }
1124 Kind::Fsst => {
1125 let (table, used) = SymbolTable::deserialize(reader.rest())?;
1126 reader.skip(used)?;
1127 let (shape, lengths) = describe_lengths(reader, count)?;
1128 reader.skip(lengths.iter().sum())?;
1129 format!("FSST[{}]({shape})", table.len())
1130 }
1131 Kind::Dict => {
1132 let entries = describe_chunk(reader)?;
1133 let codes = describe_integers(reader)?;
1134 format!("DICT({entries}, {codes})")
1135 }
1136 Kind::Front => {
1137 let prefixes = describe_integers(reader)?;
1138 let suffixes = describe_chunk(reader)?;
1139 format!("FRONT({prefixes}, {suffixes})")
1140 }
1141 Kind::Lz => {
1142 let sizes = describe_integers(reader)?;
1143 let lengths = describe_integers(reader)?;
1144 let offsets = describe_integers(reader)?;
1145 let literals = describe_chunk(reader)?;
1146 format!("LZ({sizes}, {lengths}, {offsets}, {literals})")
1147 }
1148 })
1149}
1150
1151fn describe_lengths(reader: &mut Reader<'_>, count: usize) -> Result<(String, Vec<usize>)> {
1155 let (shape, _) = integer::describe_prefix(reader.rest())?;
1156 let lengths = decode_lengths(reader, count)?;
1157 Ok((shape, lengths))
1158}
1159
1160fn encode_lengths(values: &[&[u8]], chooser: &dyn Chooser) -> Result<Vec<u8>> {
1161 let lengths: Vec<i64> = values.iter().map(|value| value.len() as i64).collect();
1162 integer::encode_with(&lengths, chooser)
1163}
1164
1165fn decode_lengths(reader: &mut Reader<'_>, count: usize) -> Result<Vec<usize>> {
1166 let lengths = decode_integers(reader)?;
1167 if lengths.len() != count {
1168 return Err(Error::internal(format!(
1169 "a string chunk says it holds {count} values and has {} lengths",
1170 lengths.len()
1171 )));
1172 }
1173 lengths
1174 .into_iter()
1175 .map(|length| {
1176 usize::try_from(length).map_err(|_| Error::internal("a negative string length"))
1177 })
1178 .collect()
1179}
1180
1181fn sum_of(lengths: &[usize]) -> Result<usize> {
1187 lengths
1188 .iter()
1189 .try_fold(0usize, |total, length| total.checked_add(*length))
1190 .ok_or_else(|| Error::internal("a string chunk longer than memory"))
1191}
1192
1193fn decode_integers(reader: &mut Reader<'_>) -> Result<Vec<i64>> {
1197 let (values, used) = integer::decode_prefix(reader.rest())?;
1198 reader.skip(used)?;
1199 Ok(values)
1200}
1201
1202fn describe_integers(reader: &mut Reader<'_>) -> Result<String> {
1203 let (text, used) = integer::describe_prefix(reader.rest())?;
1204 reader.skip(used)?;
1205 Ok(text)
1206}
1207
1208pub(crate) fn sample_of<'a>(values: &[&'a [u8]]) -> Vec<&'a [u8]> {
1227 sample_bytes_of(values, SAMPLE_BYTES)
1228}
1229
1230pub(crate) fn sample_bytes_of<'a>(values: &[&'a [u8]], budget: usize) -> Vec<&'a [u8]> {
1233 let budget = budget.max(1);
1234 let total: usize = values.iter().map(|value| value.len()).sum();
1235 if total <= budget {
1236 return values.to_vec();
1237 }
1238 let stride = total.div_ceil(budget).max(1);
1239 let span = (stride * 2 - 1).max(1) as u64;
1240 let mut state = 0x2545_f491_4f6c_dd1du64;
1241 let mut sample = Vec::with_capacity(values.len() / stride + 1);
1242 let mut at = 0usize;
1243 while at < values.len() {
1244 sample.push(values[at]);
1245 state ^= state << 13;
1246 state ^= state >> 7;
1247 state ^= state << 17;
1248 at += 1 + (state % span) as usize;
1249 }
1250 sample
1251}
1252
1253fn dictionary_of<'a>(values: &[&'a [u8]]) -> (Vec<&'a [u8]>, Vec<i64>) {
1265 let mut order: Vec<u32> = (0..values.len() as u32).collect();
1266 order.sort_unstable_by(|left, right| values[*left as usize].cmp(values[*right as usize]));
1267 let mut entries: Vec<&'a [u8]> = Vec::new();
1268 let mut codes = vec![0i64; values.len()];
1269 for &index in &order {
1270 let value = values[index as usize];
1271 if entries.last() != Some(&value) {
1272 entries.push(value);
1273 }
1274 codes[index as usize] = (entries.len() - 1) as i64;
1275 }
1276 (entries, codes)
1277}
1278
1279fn has_duplicates(values: &[&[u8]]) -> bool {
1289 let Some(slots) = values.len().checked_mul(2).map(usize::next_power_of_two) else {
1290 return false;
1291 };
1292 let mask = slots - 1;
1293 let mut table = vec![u32::MAX; slots];
1294 for (index, value) in values.iter().enumerate() {
1295 let mut at = hash_of(value) as usize & mask;
1296 loop {
1297 let held = table[at];
1298 if held == u32::MAX {
1299 table[at] = index as u32;
1300 break;
1301 }
1302 if values[held as usize] == *value {
1303 return true;
1304 }
1305 at = (at + 1) & mask;
1306 }
1307 }
1308 false
1309}
1310
1311fn hash_of(value: &[u8]) -> u64 {
1318 let mut hash = 0xcbf2_9ce4_8422_2325_u64;
1319 let mut chunks = value.chunks_exact(8);
1320 for chunk in &mut chunks {
1321 let word = u64::from_le_bytes(chunk.try_into().expect("chunks_exact(8) gives eight bytes"));
1322 hash = (hash ^ word).wrapping_mul(0x1_0000_01b3);
1323 }
1324 for byte in chunks.remainder() {
1325 hash = (hash ^ u64::from(*byte)).wrapping_mul(0x1_0000_01b3);
1326 }
1327 (hash ^ (value.len() as u64)).wrapping_mul(0x1_0000_01b3)
1328}
1329
1330fn too_long(len: usize) -> Error {
1331 Error::internal(format!("a string chunk of {len} is longer than the format allows"))
1332}
1333
1334fn put_u32(out: &mut Vec<u8>, value: u32) {
1335 out.extend_from_slice(&value.to_le_bytes());
1336}
1337
1338#[cfg(test)]
1339mod tests {
1340 use super::*;
1341
1342 fn urls(count: usize) -> Vec<Vec<u8>> {
1343 let hosts = ["www.example.com", "shop.example.com", "news.other.example.org"];
1344 let paths = ["/index.html", "/catalog/item", "/search", "/user/profile/settings"];
1345 (0..count)
1346 .map(|index| {
1347 let host = hosts[index % hosts.len()];
1348 let path = paths[(index / 3) % paths.len()];
1349 format!("http://{host}{path}?session={}&ref=google", index * 7).into_bytes()
1350 })
1351 .collect()
1352 }
1353
1354 #[test]
1357 fn the_values_at_some_positions_are_the_ones_a_whole_decode_has_there() {
1358 let values = urls(1000);
1359 let refs: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
1360 let positions = [0_u32, 3, 4, 500, 998, 999];
1361 let wanted: Vec<Vec<u8>> =
1362 positions.iter().map(|&at| values[at as usize].clone()).collect();
1363 for kind in offered(&refs) {
1364 let Some(encoded) = encode_only(kind, &refs).expect("encoded") else { continue };
1365 let flat = decode_flat_at(&encoded, &positions).expect("decoded");
1366 assert_eq!(flat.into_values(), wanted, "{kind:?}");
1367 let none = decode_flat_at(&encoded, &[]).expect("decoded");
1368 assert!(none.is_empty(), "{kind:?}");
1369 assert!(decode_flat_at(&encoded, &[4, 3]).is_err(), "{kind:?}");
1370 assert!(decode_flat_at(&encoded, &[1000]).is_err(), "{kind:?}");
1371 }
1372 let fsst = encode_only(Kind::Fsst, &refs).expect("encoded").expect("compressible");
1373 assert_eq!(decode_flat_at(&fsst, &positions).expect("decoded").into_values(), wanted);
1374 }
1375
1376 fn front_lz() -> Settled {
1377 Settled::new(vec![Kind::Front, Kind::Lz], vec![integer::Kind::Packed])
1378 }
1379
1380 #[test]
1384 fn a_block_compressed_against_the_column_table_reads_back() {
1385 let values = urls(4096);
1386 let refs: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
1387 let blocks: Vec<Vec<&[u8]>> = refs.chunks(1024).take(2).map(<[&[u8]]>::to_vec).collect();
1388 let shape = with_symbols(front_lz(), &blocks);
1389 assert!(shape.symbols(2).is_some(), "FRONT then LZ leaves FSST the third level");
1390 assert!(shape.symbols(1).is_none(), "and only that one");
1391 for block in refs.chunks(1024) {
1392 let encoded = encode_with(block, &shape).expect("encoded");
1393 assert_eq!(decode(&encoded).expect("decoded"), block.to_vec());
1394 }
1395 }
1396
1397 #[test]
1399 fn a_shape_ending_in_plain_gets_no_table() {
1400 let values = urls(1024);
1401 let refs: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
1402 let plain = Settled::new(vec![Kind::Lz, Kind::Plain], vec![integer::Kind::Packed]);
1403 let shape = with_symbols(plain, std::slice::from_ref(&refs));
1404 assert!((0..=MAX_DEPTH).all(|depth| shape.symbols(depth).is_none()));
1405 let fsst = Settled::new(vec![Kind::Fsst], vec![integer::Kind::Packed]);
1406 assert!(with_symbols(fsst, &[refs]).symbols(0).is_some());
1407 }
1408
1409 fn keyed(values: Vec<Vec<u8>>) -> Vec<Vec<u8>> {
1413 values
1414 .into_iter()
1415 .enumerate()
1416 .map(|(index, value)| {
1417 let key = (index as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15) % 1_000_000_007;
1418 let mut out = format!("{key:010}/").into_bytes();
1419 out.extend_from_slice(&value);
1420 out
1421 })
1422 .collect()
1423 }
1424
1425 fn borrow(values: &[Vec<u8>]) -> Vec<&[u8]> {
1426 values.iter().map(Vec::as_slice).collect()
1427 }
1428
1429 fn round_trip(values: &[Vec<u8>]) -> Vec<u8> {
1430 let borrowed = borrow(values);
1431 let bytes = encode(&borrowed).unwrap();
1432 let back = decode(&bytes).unwrap();
1433 assert_eq!(back, values, "{}", describe(&bytes).unwrap());
1434 check_flat(&bytes, values);
1435 bytes
1436 }
1437
1438 fn check_flat(bytes: &[u8], values: &[Vec<u8>]) {
1441 let flat = decode_flat(bytes).unwrap();
1442 let shape = describe(bytes).unwrap();
1443 assert_eq!(flat.len(), values.len(), "{shape}");
1444 assert_eq!(flat.iter().collect::<Vec<_>>(), borrow(values), "{shape}");
1445 assert_eq!(flat.bytes(), values.concat(), "{shape}");
1446 assert_eq!(flat.get(values.len()), None, "{shape}");
1447 }
1448
1449 fn kind_of(bytes: &[u8]) -> Kind {
1450 Kind::from_tag(bytes[0]).unwrap()
1451 }
1452
1453 #[test]
1454 fn every_shape_decodes_flat_to_what_it_decodes_split() {
1455 let columns =
1459 [urls(600), keyed(urls(600)), vec![b"same".to_vec(); 400], vec![Vec::new(); 7]];
1460 for values in &columns {
1461 let borrowed = borrow(values);
1462 for kind in offered(&borrowed) {
1463 let Some(bytes) = encode_only(kind, &borrowed).unwrap() else {
1464 continue;
1465 };
1466 assert_eq!(decode(&bytes).unwrap(), *values, "{}", kind.name());
1467 let flat = decode_flat(&bytes).unwrap();
1468 assert_eq!(flat.iter().collect::<Vec<_>>(), borrowed, "{}", kind.name());
1469 assert_eq!(flat.bytes(), values.concat(), "{}", kind.name());
1470 }
1471 }
1472 }
1473
1474 #[test]
1475 fn a_front_coded_chunk_that_shares_more_than_it_has_is_an_error() {
1476 let suffixes: [&[u8]; 2] = [b"abc", b"x"];
1480 let mut bytes = vec![Kind::Front.tag()];
1481 put_u32(&mut bytes, 2);
1482 bytes.extend_from_slice(&integer::encode(&[0, 9]).unwrap());
1483 bytes.extend_from_slice(&encode_only(Kind::Plain, &suffixes).unwrap().unwrap());
1484 let error = decode_flat(&bytes).expect_err("a nine byte prefix of a three byte value");
1485 assert_eq!(error.message(), "a value shares 9 bytes with a value 3 bytes long");
1486 assert_eq!(decode(&bytes).unwrap_err().message(), error.message());
1487 }
1488
1489 #[test]
1490 fn the_dictionary_is_sorted_and_the_codes_point_back_at_the_values() {
1491 let values = vec![
1494 b"pear".to_vec(),
1495 b"apple".to_vec(),
1496 b"pear".to_vec(),
1497 b"cherry".to_vec(),
1498 b"apple".to_vec(),
1499 ];
1500 let borrowed = borrow(&values);
1501 let (entries, codes) = dictionary_of(&borrowed);
1502 assert_eq!(entries, vec![b"apple".as_slice(), b"cherry".as_slice(), b"pear".as_slice()]);
1503 assert_eq!(codes, vec![2, 0, 2, 1, 0]);
1504 for (code, value) in codes.iter().zip(&borrowed) {
1505 assert_eq!(entries[*code as usize], *value);
1506 }
1507 }
1508
1509 #[test]
1510 fn a_column_with_nothing_repeated_has_no_duplicates_and_one_with_anything_does() {
1511 let distinct: Vec<Vec<u8>> =
1512 (0..5000).map(|index| format!("value-{index}").into_bytes()).collect();
1513 assert!(!has_duplicates(&borrow(&distinct)));
1514
1515 let mut repeated = distinct.clone();
1517 repeated.push(b"value-0".to_vec());
1518 assert!(has_duplicates(&borrow(&repeated)));
1519
1520 assert!(!has_duplicates(&borrow(&Vec::new())));
1521 assert!(!has_duplicates(&borrow(&[b"one".to_vec()])));
1522 assert!(has_duplicates(&borrow(&vec![b"same".to_vec(); 2])));
1523 }
1524
1525 #[test]
1526 fn long_values_that_differ_only_at_the_end_are_not_confused_for_each_other() {
1527 let stem = "http://www.example.com/a/very/long/path/that/goes/on?session=";
1530 let values: Vec<Vec<u8>> =
1531 (0..2000).map(|index| format!("{stem}{index}").into_bytes()).collect();
1532 assert!(!has_duplicates(&borrow(&values)));
1533 let (entries, codes) = dictionary_of(&borrow(&values));
1534 assert_eq!(entries.len(), values.len());
1535 assert_eq!(codes.len(), values.len());
1536 }
1537
1538 #[test]
1539 fn what_the_chooser_returns_is_the_smallest_of_what_it_was_offered() {
1540 for values in [urls(400), keyed(urls(400)), vec![b"same".to_vec(); 50], Vec::new()] {
1546 let borrowed = borrow(&values);
1547 let chosen = encode(&borrowed).unwrap();
1548 let mut smallest: Option<Vec<u8>> = None;
1549 for kind in offered(&borrowed) {
1550 let Some(bytes) = encode_only(kind, &borrowed).unwrap() else {
1551 continue;
1552 };
1553 if smallest.as_ref().is_none_or(|best| bytes.len() < best.len()) {
1554 smallest = Some(bytes);
1555 }
1556 }
1557 assert_eq!(smallest.as_deref(), Some(chosen.as_slice()), "{}", values.len());
1558 }
1559 }
1560
1561 fn raw_size(values: &[Vec<u8>]) -> usize {
1562 values.iter().map(Vec::len).sum::<usize>() + values.len() * 4
1563 }
1564
1565 #[test]
1566 fn a_matched_chunk_replays_literals_whether_or_not_they_are_compressed() {
1567 let compressed = describe(&round_trip(&keyed(urls(20_000)))).unwrap();
1573 assert!(compressed.starts_with("LZ(") && compressed.contains(", FSST["), "{compressed}");
1574
1575 let buffered = describe(&round_trip(&keyed(urls(300)))).unwrap();
1576 assert!(buffered.starts_with("LZ(") && buffered.contains(", PLAIN("), "{buffered}");
1577 }
1578
1579 #[test]
1580 fn a_copy_back_writes_what_a_byte_at_a_time_copy_writes_at_every_distance() {
1581 let seed: Vec<u8> = (0..40u8).map(|byte| byte.wrapping_mul(37).wrapping_add(11)).collect();
1585 for offset in 1..=seed.len() {
1586 for length in 1..=50 {
1587 let mut wanted = seed.clone();
1588 for _ in 0..length {
1589 wanted.push(wanted[wanted.len() - offset]);
1590 }
1591 let mut out = seed.clone();
1592 out.resize(seed.len() + length + REPLAY_SLACK, 0);
1593 let end = copy_back(&mut out, 0, seed.len(), offset, length).unwrap();
1594 assert_eq!(&out[..end], wanted.as_slice(), "offset {offset} length {length}");
1595 }
1596 }
1597 let mut short = vec![1, 2, 3, 0];
1598 assert!(copy_back(&mut short, 0, 3, 1, 2).is_err(), "past the end of the buffer");
1599 assert!(copy_back(&mut short, 0, 3, 4, 1).is_err(), "further back than the output");
1600 }
1601
1602 #[test]
1603 fn an_empty_chunk_round_trips() {
1604 let bytes = round_trip(&[]);
1605 assert_eq!(kind_of(&bytes), Kind::Plain);
1606 }
1607
1608 #[test]
1609 fn a_constant_column_costs_what_one_value_costs() {
1610 let values = vec![b"https://www.example.com/".to_vec(); 100_000];
1611 let bytes = round_trip(&values);
1612 assert_eq!(kind_of(&bytes), Kind::Constant);
1613 assert_eq!(bytes.len(), 9 + 24);
1614 }
1615
1616 #[test]
1617 fn a_url_column_of_unique_values_is_matched_rather_than_only_compressed() {
1618 let values = keyed(urls(20_000));
1626 let bytes = round_trip(&values);
1627 assert_eq!(kind_of(&bytes), Kind::Lz);
1628
1629 let borrowed: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
1632 let fsst = encode_as(Kind::Fsst, &borrowed, 0, &EXHAUSTIVE).unwrap().unwrap();
1633 assert!(bytes.len() < fsst.len(), "{} against FSST {}", bytes.len(), fsst.len());
1634
1635 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1638 assert!(ratio > 4.0, "{ratio:.2}x");
1639 }
1640
1641 #[test]
1642 fn a_sample_of_a_periodic_column_learns_every_phase_of_it() {
1643 let values = urls(20_000);
1648 let borrowed = borrow(&values);
1649 let sample = sample_of(&borrowed);
1650 let mut phases: Vec<&[u8]> = sample
1651 .iter()
1652 .map(|value| {
1653 let query =
1654 value.iter().position(|byte| *byte == b'?').expect("every value has a query");
1655 &value[..query]
1656 })
1657 .collect();
1658 phases.sort_unstable();
1659 phases.dedup();
1660 assert_eq!(phases.len(), 12);
1662 let whole = SymbolTable::train(&borrowed);
1663 let sampled = SymbolTable::train(&sample);
1664 let mut on_whole = Vec::new();
1665 let mut on_sample = Vec::new();
1666 for value in &borrowed {
1667 whole.compress(value, &mut on_whole);
1668 sampled.compress(value, &mut on_sample);
1669 }
1670 assert!(
1673 on_sample.len() < on_whole.len() * 5 / 4,
1674 "{} against {}",
1675 on_sample.len(),
1676 on_whole.len()
1677 );
1678 }
1679
1680 #[test]
1681 fn a_repeating_column_becomes_a_dictionary_of_compressed_entries() {
1682 let distinct = urls(500);
1692 let values: Vec<Vec<u8>> = (0..50_000_u64)
1693 .map(|index| {
1694 let hashed = (index.wrapping_mul(0x9E37_79B9_7F4A_7C15) >> 32) as usize;
1695 distinct[hashed % distinct.len()].clone()
1696 })
1697 .collect();
1698 let bytes = round_trip(&values);
1699 assert_eq!(kind_of(&bytes), Kind::Dict);
1700 let shape = describe(&bytes).unwrap();
1701 assert!(shape.starts_with("DICT(LZ("), "{shape}");
1702 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1703 assert!(ratio > 20.0, "{ratio:.2}x, {shape}");
1704 }
1705
1706 #[test]
1707 fn a_column_of_long_runs_costs_almost_nothing() {
1708 let distinct = urls(50);
1711 let mut values = Vec::new();
1712 for entry in &distinct {
1713 values.extend(std::iter::repeat_n(entry.clone(), 1000));
1714 }
1715 let bytes = round_trip(&values);
1716 let shape = describe(&bytes).unwrap();
1717 assert!(shape.contains("RLE"), "{shape}");
1718 assert!(bytes.len() < 2000, "{} bytes: {shape}", bytes.len());
1719 }
1720
1721 #[test]
1722 fn incompressible_strings_stay_close_to_their_own_size() {
1723 let mut state = 0x2545_f491_4f6c_dd1du64;
1726 let values: Vec<Vec<u8>> = (0..2000)
1727 .map(|_| {
1728 (0..32)
1729 .map(|_| {
1730 state ^= state << 13;
1731 state ^= state >> 7;
1732 state ^= state << 17;
1733 state as u8
1734 })
1735 .collect()
1736 })
1737 .collect();
1738 let bytes = round_trip(&values);
1739 assert!(bytes.len() < 2000 * 32 + 3000, "{} bytes", bytes.len());
1740 }
1741
1742 #[test]
1743 fn lengths_are_stored_rather_than_offsets() {
1744 let values: Vec<Vec<u8>> =
1747 (0..100_000).map(|index| format!("{index:024}").into_bytes()).collect();
1748 let borrowed = borrow(&values);
1749 let bytes = encode_only(Kind::Plain, &borrowed).unwrap().unwrap();
1750 assert_eq!(bytes.len(), 5 + 13 + 100_000 * 24);
1751 }
1752
1753 #[test]
1754 fn empty_strings_are_values_and_not_nulls() {
1755 let values = vec![Vec::new(), b"a".to_vec(), Vec::new(), b"bb".to_vec()];
1756 round_trip(&values);
1757 }
1758
1759 #[test]
1760 fn a_chunk_with_one_value_round_trips() {
1761 round_trip(&[b"only".to_vec()]);
1762 }
1763
1764 #[test]
1765 fn every_candidate_that_applies_decodes_to_the_input() {
1766 let values = urls(3000);
1767 let borrowed = borrow(&values);
1768 let applicable = candidates(&borrowed, 0);
1769 assert!(applicable.len() >= 2, "{applicable:?}");
1770 for kind in applicable {
1771 let bytes = encode_only(kind, &borrowed).unwrap().unwrap();
1772 assert_eq!(decode(&bytes).unwrap(), values, "{}", kind.name());
1773 }
1774 }
1775
1776 #[test]
1777 fn the_chooser_picks_the_smallest_candidate() {
1778 let values = urls(2000);
1779 let borrowed = borrow(&values);
1780 let chosen = encode(&borrowed).unwrap();
1781 for (_, size) in candidate_sizes(&borrowed).unwrap() {
1782 assert!(chosen.len() <= size);
1783 }
1784 }
1785
1786 #[test]
1787 fn a_truncated_chunk_is_an_error_and_not_a_panic() {
1788 let values = urls(40);
1789 let bytes = encode(&borrow(&values)).unwrap();
1790 for len in 0..bytes.len() {
1791 assert!(decode(&bytes[..len]).is_err(), "{len} bytes decoded");
1792 }
1793 }
1794
1795 #[test]
1796 fn trailing_bytes_are_an_error() {
1797 let mut bytes = encode(&borrow(&urls(10))).unwrap();
1798 bytes.push(0);
1799 let error = decode(&bytes).unwrap_err();
1800 assert!(error.message().contains("left over"), "{error}");
1801 }
1802
1803 #[test]
1804 fn an_unknown_tag_is_an_error() {
1805 let error = decode(&[99, 0, 0, 0, 0]).unwrap_err();
1806 assert!(error.message().contains("unknown string encoding tag"), "{error}");
1807 }
1808
1809 #[test]
1810 fn a_dictionary_code_outside_the_dictionary_is_an_error() {
1811 let mut bytes = vec![Kind::Dict.tag()];
1812 put_u32(&mut bytes, 1);
1813 bytes.extend_from_slice(&encode(&[b"one".as_slice()]).unwrap());
1814 bytes.extend_from_slice(&integer::encode(&[9]).unwrap());
1815 let error = decode(&bytes).unwrap_err();
1816 assert!(error.message().contains("not in the dictionary"), "{error}");
1817 }
1818
1819 #[test]
1820 fn a_sorted_column_of_urls_is_front_coded() {
1821 let mut values = urls(20_000);
1825 values.sort();
1826 let bytes = round_trip(&values);
1827 assert_eq!(kind_of(&bytes), Kind::Front);
1828 let shape = describe(&bytes).unwrap();
1829 let mut plain = Vec::new();
1830 let borrowed = borrow(&values);
1831 for (kind, size) in candidate_sizes(&borrowed).unwrap() {
1832 if kind == Kind::Fsst {
1833 plain.push(size);
1834 }
1835 }
1836 let fsst = plain[0];
1837 assert!(bytes.len() * 2 < fsst, "{} against FSST {fsst}: {shape}", bytes.len());
1838 }
1839
1840 #[test]
1841 fn a_column_with_nothing_to_share_is_not_offered_front_coding() {
1842 let mut state = 0x9e37_79b9_7f4a_7c15u64;
1845 let values: Vec<Vec<u8>> = (0..2000)
1846 .map(|_| {
1847 (0..24)
1848 .map(|_| {
1849 state ^= state << 13;
1850 state ^= state >> 7;
1851 state ^= state << 17;
1852 (state % 251) as u8
1853 })
1854 .collect()
1855 })
1856 .collect();
1857 let borrowed = borrow(&values);
1858 assert!(!candidates(&borrowed, 0).contains(&Kind::Front));
1859 }
1860
1861 #[test]
1862 fn a_prefix_longer_than_the_value_before_it_is_an_error() {
1863 let mut bytes = vec![Kind::Front.tag()];
1864 put_u32(&mut bytes, 2);
1865 bytes.extend_from_slice(&integer::encode(&[0, 9]).unwrap());
1866 bytes.extend_from_slice(&encode(&[b"one".as_slice(), b"two".as_slice()]).unwrap());
1867 let error = decode(&bytes).unwrap_err();
1868 assert!(error.message().contains("shares 9 bytes"), "{error}");
1869 }
1870
1871 #[test]
1872 fn a_negative_prefix_is_an_error() {
1873 let mut bytes = vec![Kind::Front.tag()];
1874 put_u32(&mut bytes, 1);
1875 bytes.extend_from_slice(&integer::encode(&[-1]).unwrap());
1876 bytes.extend_from_slice(&encode(&[b"one".as_slice()]).unwrap());
1877 let error = decode(&bytes).unwrap_err();
1878 assert!(error.message().contains("negative shared prefix"), "{error}");
1879 }
1880
1881 #[test]
1882 fn a_negative_length_is_an_error() {
1883 let mut bytes = vec![Kind::Plain.tag()];
1884 put_u32(&mut bytes, 1);
1885 bytes.extend_from_slice(&integer::encode(&[-1]).unwrap());
1886 let error = decode(&bytes).unwrap_err();
1887 assert!(error.message().contains("negative string length"), "{error}");
1888 }
1889
1890 #[test]
1891 fn the_sample_is_spread_across_the_chunk_and_not_taken_from_the_front() {
1892 let mut values: Vec<Vec<u8>> = Vec::new();
1895 for index in 0..20_000 {
1896 let head = if index < 10_000 { "aaaaaaaaaaaaaaaa" } else { "zzzzzzzzzzzzzzzz" };
1897 values.push(format!("{head}/{index:08}").into_bytes());
1898 }
1899 let borrowed = borrow(&values);
1900 let sample = sample_of(&borrowed);
1901 let first_half = sample.iter().filter(|value| value.starts_with(b"aaaa")).count();
1902 let second_half = sample.len() - first_half;
1903 assert!(first_half > 0 && second_half > 0, "{first_half} and {second_half}");
1904 let bytes = round_trip(&values);
1905 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1906 assert!(ratio > 4.0, "{ratio:.2}x");
1907 }
1908}