1use rudb_common::{Error, Result};
65
66use crate::chooser::{Chooser, EXHAUSTIVE};
67use crate::fsst::SymbolTable;
68use crate::integer;
69use crate::lz;
70use crate::reader::Reader;
71
72const MAX_DEPTH: u8 = 2;
75
76const SHARE_DIVISOR: usize = 20;
80
81const LZ_FLOOR: usize = 4096;
87
88pub(crate) const SAMPLE_BYTES: usize = 64 * 1024;
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum Kind {
98 Constant = 0,
100 Plain = 1,
102 Fsst = 2,
104 Dict = 3,
106 Front = 4,
108 Lz = 5,
111}
112
113impl Kind {
114 fn tag(self) -> u8 {
115 self as u8
116 }
117
118 fn from_tag(tag: u8) -> Result<Self> {
119 match tag {
120 0 => Ok(Self::Constant),
121 1 => Ok(Self::Plain),
122 2 => Ok(Self::Fsst),
123 3 => Ok(Self::Dict),
124 4 => Ok(Self::Front),
125 5 => Ok(Self::Lz),
126 other => Err(Error::internal(format!("unknown string encoding tag {other}"))),
127 }
128 }
129
130 #[must_use]
132 pub fn name(self) -> &'static str {
133 match self {
134 Self::Constant => "CONSTANT",
135 Self::Plain => "PLAIN",
136 Self::Fsst => "FSST",
137 Self::Dict => "DICT",
138 Self::Front => "FRONT",
139 Self::Lz => "LZ",
140 }
141 }
142}
143
144pub fn encode(values: &[&[u8]]) -> Result<Vec<u8>> {
155 encode_with(values, &EXHAUSTIVE)
156}
157
158pub fn encode_with(values: &[&[u8]], chooser: &dyn Chooser) -> Result<Vec<u8>> {
168 encode_at(values, 0, chooser)
169}
170
171#[derive(Debug, Clone, Default, PartialEq, Eq)]
184pub struct Flat {
185 bytes: Vec<u8>,
186 ends: Vec<usize>,
190}
191
192impl Flat {
193 fn with_capacity(count: usize, bytes: usize) -> Self {
194 Self { bytes: Vec::with_capacity(bytes), ends: Vec::with_capacity(count) }
195 }
196
197 fn push(&mut self, value: &[u8]) {
198 self.bytes.extend_from_slice(value);
199 self.ends.push(self.bytes.len());
200 }
201
202 fn start(&self, index: usize) -> usize {
204 if index == 0 { 0 } else { self.ends[index - 1] }
205 }
206
207 #[must_use]
209 pub fn len(&self) -> usize {
210 self.ends.len()
211 }
212
213 #[must_use]
215 pub fn is_empty(&self) -> bool {
216 self.ends.is_empty()
217 }
218
219 #[must_use]
222 pub fn bytes(&self) -> &[u8] {
223 &self.bytes
224 }
225
226 #[must_use]
228 pub fn get(&self, index: usize) -> Option<&[u8]> {
229 let end = *self.ends.get(index)?;
230 self.bytes.get(self.start(index)..end)
231 }
232
233 pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
235 let mut at = 0;
236 self.ends.iter().map(move |end| {
237 let value = self.bytes.get(at..*end).unwrap_or_default();
238 at = *end;
239 value
240 })
241 }
242
243 #[must_use]
245 pub fn into_bytes(self) -> Vec<u8> {
246 self.bytes
247 }
248
249 fn into_values(self) -> Vec<Vec<u8>> {
250 let mut values = Vec::with_capacity(self.len());
251 let mut at = 0;
252 for end in &self.ends {
253 values.push(self.bytes[at..*end].to_vec());
254 at = *end;
255 }
256 values
257 }
258}
259
260pub fn decode_flat(bytes: &[u8]) -> Result<Flat> {
266 let mut reader = Reader::new(bytes);
267 let flat = decode_chunk(&mut reader)?;
268 if reader.remaining() != 0 {
269 return Err(Error::internal(format!(
270 "{} bytes left over after decoding a string chunk",
271 reader.remaining()
272 )));
273 }
274 Ok(flat)
275}
276
277pub fn decode_prefix(bytes: &[u8]) -> Result<(Vec<Vec<u8>>, usize)> {
286 let mut reader = Reader::new(bytes);
287 let values = decode_chunk(&mut reader)?;
288 Ok((values.into_values(), reader.used()))
289}
290
291pub fn describe_prefix(bytes: &[u8]) -> Result<(String, usize)> {
297 let mut reader = Reader::new(bytes);
298 let text = describe_chunk(&mut reader)?;
299 Ok((text, reader.used()))
300}
301
302pub fn decode(bytes: &[u8]) -> Result<Vec<Vec<u8>>> {
308 Ok(decode_flat(bytes)?.into_values())
309}
310
311pub fn candidate_sizes(values: &[&[u8]]) -> Result<Vec<(Kind, usize)>> {
318 let mut sizes = Vec::new();
319 for kind in candidates(values, 0) {
320 if let Some(bytes) = encode_as(kind, values, 0, &EXHAUSTIVE)? {
321 sizes.push((kind, bytes.len()));
322 }
323 }
324 Ok(sizes)
325}
326
327#[must_use]
334pub fn offered(values: &[&[u8]]) -> Vec<Kind> {
335 candidates(values, 0)
336}
337
338pub fn encode_only(kind: Kind, values: &[&[u8]]) -> Result<Option<Vec<u8>>> {
350 encode_as(kind, values, 0, &EXHAUSTIVE)
351}
352
353pub(crate) fn size_as(kind: Kind, values: &[&[u8]], depth: u8) -> Result<Option<usize>> {
358 Ok(encode_as(kind, values, depth, &EXHAUSTIVE)?.map(|bytes| bytes.len()))
359}
360
361pub fn describe(bytes: &[u8]) -> Result<String> {
367 let mut reader = Reader::new(bytes);
368 describe_chunk(&mut reader)
369}
370
371fn encode_at(values: &[&[u8]], depth: u8, chooser: &dyn Chooser) -> Result<Vec<u8>> {
372 let offered = candidates(values, depth);
373 let mut best: Option<Vec<u8>> = None;
374 for kind in chooser.narrow_strings(values, &offered, depth) {
375 let Some(bytes) = encode_as(kind, values, depth, chooser)? else {
376 continue;
377 };
378 if best.as_ref().is_none_or(|current| bytes.len() < current.len()) {
379 best = Some(bytes);
380 }
381 }
382 best.ok_or_else(|| Error::internal("no string encoding applied to the chunk"))
383}
384
385fn candidates(values: &[&[u8]], depth: u8) -> Vec<Kind> {
386 let mut kinds = vec![Kind::Plain];
387 if values.is_empty() {
388 return kinds;
389 }
390 if values.iter().all(|value| *value == values[0]) {
391 return vec![Kind::Constant];
392 }
393 kinds.push(Kind::Fsst);
394 if depth < MAX_DEPTH && has_duplicates(values) {
395 kinds.push(Kind::Dict);
396 }
397 if depth < MAX_DEPTH && sharing_of(values) >= total_len(values) / SHARE_DIVISOR {
398 kinds.push(Kind::Front);
399 }
400 if depth < MAX_DEPTH && total_len(values) >= LZ_FLOOR {
401 kinds.push(Kind::Lz);
402 }
403 kinds
404}
405
406fn sharing_of(values: &[&[u8]]) -> usize {
413 let mut shared = 0;
414 for pair in values.windows(2) {
415 shared += shared_prefix(pair[0], pair[1]);
416 }
417 shared
418}
419
420pub(crate) fn front_code<'a>(values: &[&'a [u8]]) -> (Vec<i64>, Vec<&'a [u8]>) {
426 let mut prefixes = Vec::with_capacity(values.len());
427 let mut suffixes: Vec<&'a [u8]> = Vec::with_capacity(values.len());
428 let mut previous: &[u8] = b"";
429 for value in values {
430 let value: &'a [u8] = value;
431 let shared = shared_prefix(previous, value);
432 prefixes.push(shared as i64);
433 suffixes.push(&value[shared..]);
434 previous = value;
435 }
436 (prefixes, suffixes)
437}
438
439pub(crate) fn front_decode(prefixes: &[i64], suffixes: Vec<Vec<u8>>) -> Result<Vec<Vec<u8>>> {
446 let mut values: Vec<Vec<u8>> = Vec::with_capacity(suffixes.len());
447 for (index, suffix) in suffixes.into_iter().enumerate() {
448 let shared = usize::try_from(prefixes[index])
449 .map_err(|_| Error::internal("a negative shared prefix length"))?;
450 let previous: &[u8] = if index == 0 { b"" } else { &values[index - 1] };
451 if shared > previous.len() {
452 return Err(Error::internal(format!(
453 "a value shares {shared} bytes with a value {} bytes long",
454 previous.len()
455 )));
456 }
457 let mut value = Vec::with_capacity(shared + suffix.len());
458 value.extend_from_slice(&previous[..shared]);
459 value.extend_from_slice(&suffix);
460 values.push(value);
461 }
462 Ok(values)
463}
464
465fn shared_prefix(previous: &[u8], value: &[u8]) -> usize {
466 let limit = previous.len().min(value.len());
467 let mut shared = 0;
468 while shared < limit && previous[shared] == value[shared] {
469 shared += 1;
470 }
471 shared
472}
473
474fn total_len(values: &[&[u8]]) -> usize {
475 values.iter().map(|value| value.len()).sum()
476}
477
478fn encode_as(
479 kind: Kind,
480 values: &[&[u8]],
481 depth: u8,
482 chooser: &dyn Chooser,
483) -> Result<Option<Vec<u8>>> {
484 let mut out = vec![kind.tag()];
485 put_u32(&mut out, u32::try_from(values.len()).map_err(|_| too_long(values.len()))?);
486 match kind {
487 Kind::Constant => {
488 let Some(first) = values.first() else {
489 return Ok(None);
490 };
491 if values.iter().any(|value| value != first) {
492 return Ok(None);
493 }
494 put_u32(&mut out, u32::try_from(first.len()).map_err(|_| too_long(first.len()))?);
495 out.extend_from_slice(first);
496 }
497 Kind::Plain => {
498 out.extend_from_slice(&encode_lengths(values, chooser)?);
499 for value in values {
500 out.extend_from_slice(value);
501 }
502 }
503 Kind::Fsst => {
504 let sample = sample_of(values);
505 let table = SymbolTable::train(&sample);
506 if table.is_empty() {
507 return Ok(None);
508 }
509 let mut compressed = Vec::new();
510 let mut lengths = Vec::with_capacity(values.len());
511 for value in values {
512 let before = compressed.len();
513 table.compress(value, &mut compressed);
514 lengths.push((compressed.len() - before) as i64);
515 }
516 table.serialize(&mut out);
517 out.extend_from_slice(&integer::encode_with(&lengths, chooser)?);
518 out.extend_from_slice(&compressed);
519 }
520 Kind::Dict => {
521 let (entries, codes) = dictionary_of(values);
522 if entries.is_empty() {
523 return Ok(None);
524 }
525 out.extend_from_slice(&encode_at(&entries, depth + 1, chooser)?);
526 out.extend_from_slice(&integer::encode_with(&codes, chooser)?);
527 }
528 Kind::Front => {
529 let (prefixes, suffixes) = front_code(values);
530 out.extend_from_slice(&integer::encode_with(&prefixes, chooser)?);
531 out.extend_from_slice(&encode_at(&suffixes, depth + 1, chooser)?);
532 }
533 Kind::Lz => {
534 let mut joined = Vec::with_capacity(total_len(values));
535 let mut sizes = Vec::with_capacity(values.len());
536 for value in values {
537 joined.extend_from_slice(value);
538 sizes.push(value.len() as i64);
539 }
540 let tokens = lz::tokens_of(&joined);
541 out.extend_from_slice(&integer::encode_with(&sizes, chooser)?);
542 out.extend_from_slice(&integer::encode_with(&tokens.lengths, chooser)?);
543 out.extend_from_slice(&integer::encode_with(&tokens.offsets, chooser)?);
544 out.extend_from_slice(&encode_at(&tokens.literals, depth + 1, chooser)?);
545 }
546 }
547 Ok(Some(out))
548}
549
550fn decode_chunk(reader: &mut Reader<'_>) -> Result<Flat> {
551 let kind = Kind::from_tag(reader.u8()?)?;
552 let count = reader.u32()? as usize;
553 match kind {
554 Kind::Constant => {
555 let len = reader.u32()? as usize;
556 let value = reader.bytes(len)?;
557 let mut flat = Flat::with_capacity(count, len.saturating_mul(count));
558 for _ in 0..count {
559 flat.push(value);
560 }
561 Ok(flat)
562 }
563 Kind::Plain => {
564 let lengths = decode_lengths(reader, count)?;
565 let total = sum_of(&lengths)?;
568 let payload = reader.bytes(total)?;
569 let mut flat = Flat::with_capacity(count, total);
570 flat.bytes.extend_from_slice(payload);
571 let mut at = 0;
572 for length in lengths {
573 at += length;
574 flat.ends.push(at);
575 }
576 Ok(flat)
577 }
578 Kind::Fsst => {
579 let runs = read_compressed(reader, count)?;
580 let mut flat = Flat::with_capacity(count, runs.payload.len());
581 let mut at = 0;
582 for index in 0..count {
583 runs.run_into(index, &mut at, &mut flat.bytes)?;
584 flat.ends.push(flat.bytes.len());
585 }
586 Ok(flat)
587 }
588 Kind::Dict => {
589 let dictionary = decode_chunk(reader)?;
590 let codes = decode_integers(reader)?;
591 if codes.len() != count {
592 return Err(Error::internal(format!(
593 "a dictionary chunk says it holds {count} values and has {} codes",
594 codes.len()
595 )));
596 }
597 let mut flat = Flat::with_capacity(count, dictionary.bytes.len());
598 for code in codes {
599 let entry =
600 usize::try_from(code).ok().and_then(|index| dictionary.get(index)).ok_or_else(
601 || Error::internal(format!("code {code} is not in the dictionary")),
602 )?;
603 flat.push(entry);
604 }
605 Ok(flat)
606 }
607 Kind::Front => {
608 let prefixes = decode_integers(reader)?;
609 let suffixes = decode_chunk(reader)?;
610 if prefixes.len() != count || suffixes.len() != count {
611 return Err(Error::internal(format!(
612 "a front coded chunk says it holds {count} values and has {} prefixes and {} suffixes",
613 prefixes.len(),
614 suffixes.len()
615 )));
616 }
617 let mut flat = Flat::with_capacity(count, suffixes.bytes.len());
620 for (index, prefix) in prefixes.iter().enumerate() {
621 let shared = usize::try_from(*prefix)
622 .map_err(|_| Error::internal("a negative shared prefix length"))?;
623 let (from, previous) = if index == 0 {
624 (0, 0)
625 } else {
626 (flat.start(index - 1), flat.ends[index - 1] - flat.start(index - 1))
627 };
628 if shared > previous {
629 return Err(Error::internal(format!(
630 "a value shares {shared} bytes with a value {previous} bytes long"
631 )));
632 }
633 flat.bytes.extend_from_within(from..from + shared);
634 flat.bytes.extend_from_slice(suffixes.get(index).expect("in range"));
635 flat.ends.push(flat.bytes.len());
636 }
637 Ok(flat)
638 }
639 Kind::Lz => {
640 let sizes = decode_integers(reader)?;
641 let lengths = decode_integers(reader)?;
642 let offsets = decode_integers(reader)?;
643 if sizes.len() != count {
644 return Err(Error::internal(format!(
645 "a matched chunk says it holds {count} values and has {} lengths",
646 sizes.len()
647 )));
648 }
649 let mut total = 0usize;
650 let mut widths = Vec::with_capacity(count);
651 for size in sizes {
652 let width = usize::try_from(size)
653 .map_err(|_| Error::internal("a negative string length"))?;
654 total = total
655 .checked_add(width)
656 .ok_or_else(|| Error::internal("a string chunk longer than memory"))?;
657 widths.push(width);
658 }
659 let mut flat = Flat::with_capacity(count, total);
662 replay_literals(reader, &lengths, &offsets, &mut flat.bytes)?;
663 if flat.bytes.len() != total {
664 return Err(Error::internal(format!(
665 "a matched chunk rebuilt {} bytes where its lengths add up to {total}",
666 flat.bytes.len()
667 )));
668 }
669 let mut at = 0;
670 for width in widths {
671 at += width;
672 flat.ends.push(at);
673 }
674 Ok(flat)
675 }
676 }
677}
678
679struct Compressed<'a> {
685 table: SymbolTable,
687 lengths: Vec<usize>,
689 payload: &'a [u8],
691}
692
693impl Compressed<'_> {
694 fn run_into(&self, index: usize, at: &mut usize, out: &mut Vec<u8>) -> Result<()> {
703 let length = *self
704 .lengths
705 .get(index)
706 .ok_or_else(|| Error::internal(format!("run {index} is not in the chunk")))?;
707 let end = at
708 .checked_add(length)
709 .ok_or_else(|| Error::internal("a compressed chunk longer than memory"))?;
710 let run = self
711 .payload
712 .get(*at..end)
713 .ok_or_else(|| Error::internal("a compressed run is past the end of its chunk"))?;
714 *at = end;
715 self.table.decompress(run, out)
716 }
717}
718
719fn read_compressed<'a>(reader: &mut Reader<'a>, count: usize) -> Result<Compressed<'a>> {
728 let (table, used) = SymbolTable::deserialize(reader.rest())?;
729 reader.skip(used)?;
730 let lengths = decode_lengths(reader, count)?;
731 let compressed_len = sum_of(&lengths)?;
734 if compressed_len > reader.remaining() {
735 return Err(Error::internal(format!(
736 "a compressed chunk says it holds {compressed_len} bytes and has {}",
737 reader.remaining()
738 )));
739 }
740 let payload = reader.bytes(compressed_len)?;
741 Ok(Compressed { table, lengths, payload })
742}
743
744fn replay_literals(
757 reader: &mut Reader<'_>,
758 lengths: &[i64],
759 offsets: &[i64],
760 out: &mut Vec<u8>,
761) -> Result<()> {
762 if reader.rest().first() == Some(&Kind::Fsst.tag()) {
763 reader.u8()?;
764 let runs = reader.u32()? as usize;
765 let compressed = read_compressed(reader, runs)?;
766 let mut at = 0;
767 return lz::replay(
768 runs,
769 |index, into| compressed.run_into(index, &mut at, into),
770 lengths,
771 offsets,
772 out,
773 );
774 }
775 let literals = decode_chunk(reader)?;
776 lz::rebuild_into(&literals, lengths, offsets, out)
777}
778
779fn describe_chunk(reader: &mut Reader<'_>) -> Result<String> {
780 let kind = Kind::from_tag(reader.u8()?)?;
781 let count = reader.u32()? as usize;
782 Ok(match kind {
783 Kind::Constant => {
784 let len = reader.u32()? as usize;
785 reader.bytes(len)?;
786 "CONSTANT".to_string()
787 }
788 Kind::Plain => {
789 let (shape, lengths) = describe_lengths(reader, count)?;
790 reader.skip(lengths.iter().sum())?;
791 format!("PLAIN({shape})")
792 }
793 Kind::Fsst => {
794 let (table, used) = SymbolTable::deserialize(reader.rest())?;
795 reader.skip(used)?;
796 let (shape, lengths) = describe_lengths(reader, count)?;
797 reader.skip(lengths.iter().sum())?;
798 format!("FSST[{}]({shape})", table.len())
799 }
800 Kind::Dict => {
801 let entries = describe_chunk(reader)?;
802 let codes = describe_integers(reader)?;
803 format!("DICT({entries}, {codes})")
804 }
805 Kind::Front => {
806 let prefixes = describe_integers(reader)?;
807 let suffixes = describe_chunk(reader)?;
808 format!("FRONT({prefixes}, {suffixes})")
809 }
810 Kind::Lz => {
811 let sizes = describe_integers(reader)?;
812 let lengths = describe_integers(reader)?;
813 let offsets = describe_integers(reader)?;
814 let literals = describe_chunk(reader)?;
815 format!("LZ({sizes}, {lengths}, {offsets}, {literals})")
816 }
817 })
818}
819
820fn describe_lengths(reader: &mut Reader<'_>, count: usize) -> Result<(String, Vec<usize>)> {
824 let (shape, _) = integer::describe_prefix(reader.rest())?;
825 let lengths = decode_lengths(reader, count)?;
826 Ok((shape, lengths))
827}
828
829fn encode_lengths(values: &[&[u8]], chooser: &dyn Chooser) -> Result<Vec<u8>> {
830 let lengths: Vec<i64> = values.iter().map(|value| value.len() as i64).collect();
831 integer::encode_with(&lengths, chooser)
832}
833
834fn decode_lengths(reader: &mut Reader<'_>, count: usize) -> Result<Vec<usize>> {
835 let lengths = decode_integers(reader)?;
836 if lengths.len() != count {
837 return Err(Error::internal(format!(
838 "a string chunk says it holds {count} values and has {} lengths",
839 lengths.len()
840 )));
841 }
842 lengths
843 .into_iter()
844 .map(|length| {
845 usize::try_from(length).map_err(|_| Error::internal("a negative string length"))
846 })
847 .collect()
848}
849
850fn sum_of(lengths: &[usize]) -> Result<usize> {
856 lengths
857 .iter()
858 .try_fold(0usize, |total, length| total.checked_add(*length))
859 .ok_or_else(|| Error::internal("a string chunk longer than memory"))
860}
861
862fn decode_integers(reader: &mut Reader<'_>) -> Result<Vec<i64>> {
866 let (values, used) = integer::decode_prefix(reader.rest())?;
867 reader.skip(used)?;
868 Ok(values)
869}
870
871fn describe_integers(reader: &mut Reader<'_>) -> Result<String> {
872 let (text, used) = integer::describe_prefix(reader.rest())?;
873 reader.skip(used)?;
874 Ok(text)
875}
876
877pub(crate) fn sample_of<'a>(values: &[&'a [u8]]) -> Vec<&'a [u8]> {
896 sample_bytes_of(values, SAMPLE_BYTES)
897}
898
899pub(crate) fn sample_bytes_of<'a>(values: &[&'a [u8]], budget: usize) -> Vec<&'a [u8]> {
902 let budget = budget.max(1);
903 let total: usize = values.iter().map(|value| value.len()).sum();
904 if total <= budget {
905 return values.to_vec();
906 }
907 let stride = total.div_ceil(budget).max(1);
908 let span = (stride * 2 - 1).max(1) as u64;
909 let mut state = 0x2545_f491_4f6c_dd1du64;
910 let mut sample = Vec::with_capacity(values.len() / stride + 1);
911 let mut at = 0usize;
912 while at < values.len() {
913 sample.push(values[at]);
914 state ^= state << 13;
915 state ^= state >> 7;
916 state ^= state << 17;
917 at += 1 + (state % span) as usize;
918 }
919 sample
920}
921
922fn dictionary_of<'a>(values: &[&'a [u8]]) -> (Vec<&'a [u8]>, Vec<i64>) {
934 let mut order: Vec<u32> = (0..values.len() as u32).collect();
935 order.sort_unstable_by(|left, right| values[*left as usize].cmp(values[*right as usize]));
936 let mut entries: Vec<&'a [u8]> = Vec::new();
937 let mut codes = vec![0i64; values.len()];
938 for &index in &order {
939 let value = values[index as usize];
940 if entries.last() != Some(&value) {
941 entries.push(value);
942 }
943 codes[index as usize] = (entries.len() - 1) as i64;
944 }
945 (entries, codes)
946}
947
948fn has_duplicates(values: &[&[u8]]) -> bool {
958 let Some(slots) = values.len().checked_mul(2).map(usize::next_power_of_two) else {
959 return false;
960 };
961 let mask = slots - 1;
962 let mut table = vec![u32::MAX; slots];
963 for (index, value) in values.iter().enumerate() {
964 let mut at = hash_of(value) as usize & mask;
965 loop {
966 let held = table[at];
967 if held == u32::MAX {
968 table[at] = index as u32;
969 break;
970 }
971 if values[held as usize] == *value {
972 return true;
973 }
974 at = (at + 1) & mask;
975 }
976 }
977 false
978}
979
980fn hash_of(value: &[u8]) -> u64 {
987 let mut hash = 0xcbf2_9ce4_8422_2325_u64;
988 let mut chunks = value.chunks_exact(8);
989 for chunk in &mut chunks {
990 let word = u64::from_le_bytes(chunk.try_into().expect("chunks_exact(8) gives eight bytes"));
991 hash = (hash ^ word).wrapping_mul(0x1_0000_01b3);
992 }
993 for byte in chunks.remainder() {
994 hash = (hash ^ u64::from(*byte)).wrapping_mul(0x1_0000_01b3);
995 }
996 (hash ^ (value.len() as u64)).wrapping_mul(0x1_0000_01b3)
997}
998
999fn too_long(len: usize) -> Error {
1000 Error::internal(format!("a string chunk of {len} is longer than the format allows"))
1001}
1002
1003fn put_u32(out: &mut Vec<u8>, value: u32) {
1004 out.extend_from_slice(&value.to_le_bytes());
1005}
1006
1007#[cfg(test)]
1008mod tests {
1009 use super::*;
1010
1011 fn urls(count: usize) -> Vec<Vec<u8>> {
1012 let hosts = ["www.example.com", "shop.example.com", "news.other.example.org"];
1013 let paths = ["/index.html", "/catalog/item", "/search", "/user/profile/settings"];
1014 (0..count)
1015 .map(|index| {
1016 let host = hosts[index % hosts.len()];
1017 let path = paths[(index / 3) % paths.len()];
1018 format!("http://{host}{path}?session={}&ref=google", index * 7).into_bytes()
1019 })
1020 .collect()
1021 }
1022
1023 fn keyed(values: Vec<Vec<u8>>) -> Vec<Vec<u8>> {
1027 values
1028 .into_iter()
1029 .enumerate()
1030 .map(|(index, value)| {
1031 let key = (index as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15) % 1_000_000_007;
1032 let mut out = format!("{key:010}/").into_bytes();
1033 out.extend_from_slice(&value);
1034 out
1035 })
1036 .collect()
1037 }
1038
1039 fn borrow(values: &[Vec<u8>]) -> Vec<&[u8]> {
1040 values.iter().map(Vec::as_slice).collect()
1041 }
1042
1043 fn round_trip(values: &[Vec<u8>]) -> Vec<u8> {
1044 let borrowed = borrow(values);
1045 let bytes = encode(&borrowed).unwrap();
1046 let back = decode(&bytes).unwrap();
1047 assert_eq!(back, values, "{}", describe(&bytes).unwrap());
1048 check_flat(&bytes, values);
1049 bytes
1050 }
1051
1052 fn check_flat(bytes: &[u8], values: &[Vec<u8>]) {
1055 let flat = decode_flat(bytes).unwrap();
1056 let shape = describe(bytes).unwrap();
1057 assert_eq!(flat.len(), values.len(), "{shape}");
1058 assert_eq!(flat.iter().collect::<Vec<_>>(), borrow(values), "{shape}");
1059 assert_eq!(flat.bytes(), values.concat(), "{shape}");
1060 assert_eq!(flat.get(values.len()), None, "{shape}");
1061 }
1062
1063 fn kind_of(bytes: &[u8]) -> Kind {
1064 Kind::from_tag(bytes[0]).unwrap()
1065 }
1066
1067 #[test]
1068 fn every_shape_decodes_flat_to_what_it_decodes_split() {
1069 let columns =
1073 [urls(600), keyed(urls(600)), vec![b"same".to_vec(); 400], vec![Vec::new(); 7]];
1074 for values in &columns {
1075 let borrowed = borrow(values);
1076 for kind in offered(&borrowed) {
1077 let Some(bytes) = encode_only(kind, &borrowed).unwrap() else {
1078 continue;
1079 };
1080 assert_eq!(decode(&bytes).unwrap(), *values, "{}", kind.name());
1081 let flat = decode_flat(&bytes).unwrap();
1082 assert_eq!(flat.iter().collect::<Vec<_>>(), borrowed, "{}", kind.name());
1083 assert_eq!(flat.bytes(), values.concat(), "{}", kind.name());
1084 }
1085 }
1086 }
1087
1088 #[test]
1089 fn a_front_coded_chunk_that_shares_more_than_it_has_is_an_error() {
1090 let suffixes: [&[u8]; 2] = [b"abc", b"x"];
1094 let mut bytes = vec![Kind::Front.tag()];
1095 put_u32(&mut bytes, 2);
1096 bytes.extend_from_slice(&integer::encode(&[0, 9]).unwrap());
1097 bytes.extend_from_slice(&encode_only(Kind::Plain, &suffixes).unwrap().unwrap());
1098 let error = decode_flat(&bytes).expect_err("a nine byte prefix of a three byte value");
1099 assert_eq!(error.message(), "a value shares 9 bytes with a value 3 bytes long");
1100 assert_eq!(decode(&bytes).unwrap_err().message(), error.message());
1101 }
1102
1103 #[test]
1104 fn the_dictionary_is_sorted_and_the_codes_point_back_at_the_values() {
1105 let values = vec![
1108 b"pear".to_vec(),
1109 b"apple".to_vec(),
1110 b"pear".to_vec(),
1111 b"cherry".to_vec(),
1112 b"apple".to_vec(),
1113 ];
1114 let borrowed = borrow(&values);
1115 let (entries, codes) = dictionary_of(&borrowed);
1116 assert_eq!(entries, vec![b"apple".as_slice(), b"cherry".as_slice(), b"pear".as_slice()]);
1117 assert_eq!(codes, vec![2, 0, 2, 1, 0]);
1118 for (code, value) in codes.iter().zip(&borrowed) {
1119 assert_eq!(entries[*code as usize], *value);
1120 }
1121 }
1122
1123 #[test]
1124 fn a_column_with_nothing_repeated_has_no_duplicates_and_one_with_anything_does() {
1125 let distinct: Vec<Vec<u8>> =
1126 (0..5000).map(|index| format!("value-{index}").into_bytes()).collect();
1127 assert!(!has_duplicates(&borrow(&distinct)));
1128
1129 let mut repeated = distinct.clone();
1131 repeated.push(b"value-0".to_vec());
1132 assert!(has_duplicates(&borrow(&repeated)));
1133
1134 assert!(!has_duplicates(&borrow(&Vec::new())));
1135 assert!(!has_duplicates(&borrow(&[b"one".to_vec()])));
1136 assert!(has_duplicates(&borrow(&vec![b"same".to_vec(); 2])));
1137 }
1138
1139 #[test]
1140 fn long_values_that_differ_only_at_the_end_are_not_confused_for_each_other() {
1141 let stem = "http://www.example.com/a/very/long/path/that/goes/on?session=";
1144 let values: Vec<Vec<u8>> =
1145 (0..2000).map(|index| format!("{stem}{index}").into_bytes()).collect();
1146 assert!(!has_duplicates(&borrow(&values)));
1147 let (entries, codes) = dictionary_of(&borrow(&values));
1148 assert_eq!(entries.len(), values.len());
1149 assert_eq!(codes.len(), values.len());
1150 }
1151
1152 #[test]
1153 fn what_the_chooser_returns_is_the_smallest_of_what_it_was_offered() {
1154 for values in [urls(400), keyed(urls(400)), vec![b"same".to_vec(); 50], Vec::new()] {
1160 let borrowed = borrow(&values);
1161 let chosen = encode(&borrowed).unwrap();
1162 let mut smallest: Option<Vec<u8>> = None;
1163 for kind in offered(&borrowed) {
1164 let Some(bytes) = encode_only(kind, &borrowed).unwrap() else {
1165 continue;
1166 };
1167 if smallest.as_ref().is_none_or(|best| bytes.len() < best.len()) {
1168 smallest = Some(bytes);
1169 }
1170 }
1171 assert_eq!(smallest.as_deref(), Some(chosen.as_slice()), "{}", values.len());
1172 }
1173 }
1174
1175 fn raw_size(values: &[Vec<u8>]) -> usize {
1176 values.iter().map(Vec::len).sum::<usize>() + values.len() * 4
1177 }
1178
1179 #[test]
1180 fn a_matched_chunk_replays_literals_whether_or_not_they_are_compressed() {
1181 let compressed = describe(&round_trip(&keyed(urls(20_000)))).unwrap();
1187 assert!(compressed.starts_with("LZ(") && compressed.contains(", FSST["), "{compressed}");
1188
1189 let buffered = describe(&round_trip(&keyed(urls(300)))).unwrap();
1190 assert!(buffered.starts_with("LZ(") && buffered.contains(", PLAIN("), "{buffered}");
1191 }
1192
1193 #[test]
1194 fn an_empty_chunk_round_trips() {
1195 let bytes = round_trip(&[]);
1196 assert_eq!(kind_of(&bytes), Kind::Plain);
1197 }
1198
1199 #[test]
1200 fn a_constant_column_costs_what_one_value_costs() {
1201 let values = vec![b"https://www.example.com/".to_vec(); 100_000];
1202 let bytes = round_trip(&values);
1203 assert_eq!(kind_of(&bytes), Kind::Constant);
1204 assert_eq!(bytes.len(), 9 + 24);
1205 }
1206
1207 #[test]
1208 fn a_url_column_of_unique_values_is_matched_rather_than_only_compressed() {
1209 let values = keyed(urls(20_000));
1217 let bytes = round_trip(&values);
1218 assert_eq!(kind_of(&bytes), Kind::Lz);
1219
1220 let borrowed: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
1223 let fsst = encode_as(Kind::Fsst, &borrowed, 0, &EXHAUSTIVE).unwrap().unwrap();
1224 assert!(bytes.len() < fsst.len(), "{} against FSST {}", bytes.len(), fsst.len());
1225
1226 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1229 assert!(ratio > 4.0, "{ratio:.2}x");
1230 }
1231
1232 #[test]
1233 fn a_sample_of_a_periodic_column_learns_every_phase_of_it() {
1234 let values = urls(20_000);
1239 let borrowed = borrow(&values);
1240 let sample = sample_of(&borrowed);
1241 let mut phases: Vec<&[u8]> = sample
1242 .iter()
1243 .map(|value| {
1244 let query =
1245 value.iter().position(|byte| *byte == b'?').expect("every value has a query");
1246 &value[..query]
1247 })
1248 .collect();
1249 phases.sort_unstable();
1250 phases.dedup();
1251 assert_eq!(phases.len(), 12);
1253 let whole = SymbolTable::train(&borrowed);
1254 let sampled = SymbolTable::train(&sample);
1255 let mut on_whole = Vec::new();
1256 let mut on_sample = Vec::new();
1257 for value in &borrowed {
1258 whole.compress(value, &mut on_whole);
1259 sampled.compress(value, &mut on_sample);
1260 }
1261 assert!(
1264 on_sample.len() < on_whole.len() * 5 / 4,
1265 "{} against {}",
1266 on_sample.len(),
1267 on_whole.len()
1268 );
1269 }
1270
1271 #[test]
1272 fn a_repeating_column_becomes_a_dictionary_of_compressed_entries() {
1273 let distinct = urls(500);
1279 let values: Vec<Vec<u8>> =
1280 (0..50_000).map(|index| distinct[index * 7919 % distinct.len()].clone()).collect();
1281 let bytes = round_trip(&values);
1282 assert_eq!(kind_of(&bytes), Kind::Dict);
1283 let shape = describe(&bytes).unwrap();
1284 assert!(shape.starts_with("DICT(LZ("), "{shape}");
1285 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1286 assert!(ratio > 20.0, "{ratio:.2}x, {shape}");
1287 }
1288
1289 #[test]
1290 fn a_column_of_long_runs_costs_almost_nothing() {
1291 let distinct = urls(50);
1294 let mut values = Vec::new();
1295 for entry in &distinct {
1296 values.extend(std::iter::repeat_n(entry.clone(), 1000));
1297 }
1298 let bytes = round_trip(&values);
1299 let shape = describe(&bytes).unwrap();
1300 assert!(shape.contains("RLE"), "{shape}");
1301 assert!(bytes.len() < 2000, "{} bytes: {shape}", bytes.len());
1302 }
1303
1304 #[test]
1305 fn incompressible_strings_stay_close_to_their_own_size() {
1306 let mut state = 0x2545_f491_4f6c_dd1du64;
1309 let values: Vec<Vec<u8>> = (0..2000)
1310 .map(|_| {
1311 (0..32)
1312 .map(|_| {
1313 state ^= state << 13;
1314 state ^= state >> 7;
1315 state ^= state << 17;
1316 state as u8
1317 })
1318 .collect()
1319 })
1320 .collect();
1321 let bytes = round_trip(&values);
1322 assert!(bytes.len() < 2000 * 32 + 3000, "{} bytes", bytes.len());
1323 }
1324
1325 #[test]
1326 fn lengths_are_stored_rather_than_offsets() {
1327 let values: Vec<Vec<u8>> =
1330 (0..100_000).map(|index| format!("{index:024}").into_bytes()).collect();
1331 let borrowed = borrow(&values);
1332 let bytes = encode_only(Kind::Plain, &borrowed).unwrap().unwrap();
1333 assert_eq!(bytes.len(), 5 + 13 + 100_000 * 24);
1334 }
1335
1336 #[test]
1337 fn empty_strings_are_values_and_not_nulls() {
1338 let values = vec![Vec::new(), b"a".to_vec(), Vec::new(), b"bb".to_vec()];
1339 round_trip(&values);
1340 }
1341
1342 #[test]
1343 fn a_chunk_with_one_value_round_trips() {
1344 round_trip(&[b"only".to_vec()]);
1345 }
1346
1347 #[test]
1348 fn every_candidate_that_applies_decodes_to_the_input() {
1349 let values = urls(3000);
1350 let borrowed = borrow(&values);
1351 let applicable = candidates(&borrowed, 0);
1352 assert!(applicable.len() >= 2, "{applicable:?}");
1353 for kind in applicable {
1354 let bytes = encode_only(kind, &borrowed).unwrap().unwrap();
1355 assert_eq!(decode(&bytes).unwrap(), values, "{}", kind.name());
1356 }
1357 }
1358
1359 #[test]
1360 fn the_chooser_picks_the_smallest_candidate() {
1361 let values = urls(2000);
1362 let borrowed = borrow(&values);
1363 let chosen = encode(&borrowed).unwrap();
1364 for (_, size) in candidate_sizes(&borrowed).unwrap() {
1365 assert!(chosen.len() <= size);
1366 }
1367 }
1368
1369 #[test]
1370 fn a_truncated_chunk_is_an_error_and_not_a_panic() {
1371 let values = urls(40);
1372 let bytes = encode(&borrow(&values)).unwrap();
1373 for len in 0..bytes.len() {
1374 assert!(decode(&bytes[..len]).is_err(), "{len} bytes decoded");
1375 }
1376 }
1377
1378 #[test]
1379 fn trailing_bytes_are_an_error() {
1380 let mut bytes = encode(&borrow(&urls(10))).unwrap();
1381 bytes.push(0);
1382 let error = decode(&bytes).unwrap_err();
1383 assert!(error.message().contains("left over"), "{error}");
1384 }
1385
1386 #[test]
1387 fn an_unknown_tag_is_an_error() {
1388 let error = decode(&[99, 0, 0, 0, 0]).unwrap_err();
1389 assert!(error.message().contains("unknown string encoding tag"), "{error}");
1390 }
1391
1392 #[test]
1393 fn a_dictionary_code_outside_the_dictionary_is_an_error() {
1394 let mut bytes = vec![Kind::Dict.tag()];
1395 put_u32(&mut bytes, 1);
1396 bytes.extend_from_slice(&encode(&[b"one".as_slice()]).unwrap());
1397 bytes.extend_from_slice(&integer::encode(&[9]).unwrap());
1398 let error = decode(&bytes).unwrap_err();
1399 assert!(error.message().contains("not in the dictionary"), "{error}");
1400 }
1401
1402 #[test]
1403 fn a_sorted_column_of_urls_is_front_coded() {
1404 let mut values = urls(20_000);
1408 values.sort();
1409 let bytes = round_trip(&values);
1410 assert_eq!(kind_of(&bytes), Kind::Front);
1411 let shape = describe(&bytes).unwrap();
1412 let mut plain = Vec::new();
1413 let borrowed = borrow(&values);
1414 for (kind, size) in candidate_sizes(&borrowed).unwrap() {
1415 if kind == Kind::Fsst {
1416 plain.push(size);
1417 }
1418 }
1419 let fsst = plain[0];
1420 assert!(bytes.len() * 2 < fsst, "{} against FSST {fsst}: {shape}", bytes.len());
1421 }
1422
1423 #[test]
1424 fn a_column_with_nothing_to_share_is_not_offered_front_coding() {
1425 let mut state = 0x9e37_79b9_7f4a_7c15u64;
1428 let values: Vec<Vec<u8>> = (0..2000)
1429 .map(|_| {
1430 (0..24)
1431 .map(|_| {
1432 state ^= state << 13;
1433 state ^= state >> 7;
1434 state ^= state << 17;
1435 (state % 251) as u8
1436 })
1437 .collect()
1438 })
1439 .collect();
1440 let borrowed = borrow(&values);
1441 assert!(!candidates(&borrowed, 0).contains(&Kind::Front));
1442 }
1443
1444 #[test]
1445 fn a_prefix_longer_than_the_value_before_it_is_an_error() {
1446 let mut bytes = vec![Kind::Front.tag()];
1447 put_u32(&mut bytes, 2);
1448 bytes.extend_from_slice(&integer::encode(&[0, 9]).unwrap());
1449 bytes.extend_from_slice(&encode(&[b"one".as_slice(), b"two".as_slice()]).unwrap());
1450 let error = decode(&bytes).unwrap_err();
1451 assert!(error.message().contains("shares 9 bytes"), "{error}");
1452 }
1453
1454 #[test]
1455 fn a_negative_prefix_is_an_error() {
1456 let mut bytes = vec![Kind::Front.tag()];
1457 put_u32(&mut bytes, 1);
1458 bytes.extend_from_slice(&integer::encode(&[-1]).unwrap());
1459 bytes.extend_from_slice(&encode(&[b"one".as_slice()]).unwrap());
1460 let error = decode(&bytes).unwrap_err();
1461 assert!(error.message().contains("negative shared prefix"), "{error}");
1462 }
1463
1464 #[test]
1465 fn a_negative_length_is_an_error() {
1466 let mut bytes = vec![Kind::Plain.tag()];
1467 put_u32(&mut bytes, 1);
1468 bytes.extend_from_slice(&integer::encode(&[-1]).unwrap());
1469 let error = decode(&bytes).unwrap_err();
1470 assert!(error.message().contains("negative string length"), "{error}");
1471 }
1472
1473 #[test]
1474 fn the_sample_is_spread_across_the_chunk_and_not_taken_from_the_front() {
1475 let mut values: Vec<Vec<u8>> = Vec::new();
1478 for index in 0..20_000 {
1479 let head = if index < 10_000 { "aaaaaaaaaaaaaaaa" } else { "zzzzzzzzzzzzzzzz" };
1480 values.push(format!("{head}/{index:08}").into_bytes());
1481 }
1482 let borrowed = borrow(&values);
1483 let sample = sample_of(&borrowed);
1484 let first_half = sample.iter().filter(|value| value.starts_with(b"aaaa")).count();
1485 let second_half = sample.len() - first_half;
1486 assert!(first_half > 0 && second_half > 0, "{first_half} and {second_half}");
1487 let bytes = round_trip(&values);
1488 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1489 assert!(ratio > 4.0, "{ratio:.2}x");
1490 }
1491}