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 (table, used) = SymbolTable::deserialize(reader.rest())?;
580 reader.skip(used)?;
581 let lengths = decode_lengths(reader, count)?;
582 let compressed_len = sum_of(&lengths)?;
585 if compressed_len > reader.remaining() {
586 return Err(Error::internal(format!(
587 "a compressed chunk says it holds {compressed_len} bytes and has {}",
588 reader.remaining()
589 )));
590 }
591 let mut flat = Flat::with_capacity(count, compressed_len);
592 for length in lengths {
593 let compressed = reader.bytes(length)?;
594 table.decompress(compressed, &mut flat.bytes)?;
595 flat.ends.push(flat.bytes.len());
596 }
597 Ok(flat)
598 }
599 Kind::Dict => {
600 let dictionary = decode_chunk(reader)?;
601 let codes = decode_integers(reader)?;
602 if codes.len() != count {
603 return Err(Error::internal(format!(
604 "a dictionary chunk says it holds {count} values and has {} codes",
605 codes.len()
606 )));
607 }
608 let mut flat = Flat::with_capacity(count, dictionary.bytes.len());
609 for code in codes {
610 let entry =
611 usize::try_from(code).ok().and_then(|index| dictionary.get(index)).ok_or_else(
612 || Error::internal(format!("code {code} is not in the dictionary")),
613 )?;
614 flat.push(entry);
615 }
616 Ok(flat)
617 }
618 Kind::Front => {
619 let prefixes = decode_integers(reader)?;
620 let suffixes = decode_chunk(reader)?;
621 if prefixes.len() != count || suffixes.len() != count {
622 return Err(Error::internal(format!(
623 "a front coded chunk says it holds {count} values and has {} prefixes and {} suffixes",
624 prefixes.len(),
625 suffixes.len()
626 )));
627 }
628 let mut flat = Flat::with_capacity(count, suffixes.bytes.len());
631 for (index, prefix) in prefixes.iter().enumerate() {
632 let shared = usize::try_from(*prefix)
633 .map_err(|_| Error::internal("a negative shared prefix length"))?;
634 let (from, previous) = if index == 0 {
635 (0, 0)
636 } else {
637 (flat.start(index - 1), flat.ends[index - 1] - flat.start(index - 1))
638 };
639 if shared > previous {
640 return Err(Error::internal(format!(
641 "a value shares {shared} bytes with a value {previous} bytes long"
642 )));
643 }
644 flat.bytes.extend_from_within(from..from + shared);
645 flat.bytes.extend_from_slice(suffixes.get(index).expect("in range"));
646 flat.ends.push(flat.bytes.len());
647 }
648 Ok(flat)
649 }
650 Kind::Lz => {
651 let sizes = decode_integers(reader)?;
652 let lengths = decode_integers(reader)?;
653 let offsets = decode_integers(reader)?;
654 let literals = decode_chunk(reader)?;
655 if sizes.len() != count {
656 return Err(Error::internal(format!(
657 "a matched chunk says it holds {count} values and has {} lengths",
658 sizes.len()
659 )));
660 }
661 let mut total = 0usize;
662 let mut widths = Vec::with_capacity(count);
663 for size in sizes {
664 let width = usize::try_from(size)
665 .map_err(|_| Error::internal("a negative string length"))?;
666 total = total
667 .checked_add(width)
668 .ok_or_else(|| Error::internal("a string chunk longer than memory"))?;
669 widths.push(width);
670 }
671 let mut flat = Flat::with_capacity(count, total);
674 lz::rebuild_into(&literals, &lengths, &offsets, &mut flat.bytes)?;
675 if flat.bytes.len() != total {
676 return Err(Error::internal(format!(
677 "a matched chunk rebuilt {} bytes where its lengths add up to {total}",
678 flat.bytes.len()
679 )));
680 }
681 let mut at = 0;
682 for width in widths {
683 at += width;
684 flat.ends.push(at);
685 }
686 Ok(flat)
687 }
688 }
689}
690
691fn describe_chunk(reader: &mut Reader<'_>) -> Result<String> {
692 let kind = Kind::from_tag(reader.u8()?)?;
693 let count = reader.u32()? as usize;
694 Ok(match kind {
695 Kind::Constant => {
696 let len = reader.u32()? as usize;
697 reader.bytes(len)?;
698 "CONSTANT".to_string()
699 }
700 Kind::Plain => {
701 let (shape, lengths) = describe_lengths(reader, count)?;
702 reader.skip(lengths.iter().sum())?;
703 format!("PLAIN({shape})")
704 }
705 Kind::Fsst => {
706 let (table, used) = SymbolTable::deserialize(reader.rest())?;
707 reader.skip(used)?;
708 let (shape, lengths) = describe_lengths(reader, count)?;
709 reader.skip(lengths.iter().sum())?;
710 format!("FSST[{}]({shape})", table.len())
711 }
712 Kind::Dict => {
713 let entries = describe_chunk(reader)?;
714 let codes = describe_integers(reader)?;
715 format!("DICT({entries}, {codes})")
716 }
717 Kind::Front => {
718 let prefixes = describe_integers(reader)?;
719 let suffixes = describe_chunk(reader)?;
720 format!("FRONT({prefixes}, {suffixes})")
721 }
722 Kind::Lz => {
723 let sizes = describe_integers(reader)?;
724 let lengths = describe_integers(reader)?;
725 let offsets = describe_integers(reader)?;
726 let literals = describe_chunk(reader)?;
727 format!("LZ({sizes}, {lengths}, {offsets}, {literals})")
728 }
729 })
730}
731
732fn describe_lengths(reader: &mut Reader<'_>, count: usize) -> Result<(String, Vec<usize>)> {
736 let (shape, _) = integer::describe_prefix(reader.rest())?;
737 let lengths = decode_lengths(reader, count)?;
738 Ok((shape, lengths))
739}
740
741fn encode_lengths(values: &[&[u8]], chooser: &dyn Chooser) -> Result<Vec<u8>> {
742 let lengths: Vec<i64> = values.iter().map(|value| value.len() as i64).collect();
743 integer::encode_with(&lengths, chooser)
744}
745
746fn decode_lengths(reader: &mut Reader<'_>, count: usize) -> Result<Vec<usize>> {
747 let lengths = decode_integers(reader)?;
748 if lengths.len() != count {
749 return Err(Error::internal(format!(
750 "a string chunk says it holds {count} values and has {} lengths",
751 lengths.len()
752 )));
753 }
754 lengths
755 .into_iter()
756 .map(|length| {
757 usize::try_from(length).map_err(|_| Error::internal("a negative string length"))
758 })
759 .collect()
760}
761
762fn sum_of(lengths: &[usize]) -> Result<usize> {
768 lengths
769 .iter()
770 .try_fold(0usize, |total, length| total.checked_add(*length))
771 .ok_or_else(|| Error::internal("a string chunk longer than memory"))
772}
773
774fn decode_integers(reader: &mut Reader<'_>) -> Result<Vec<i64>> {
778 let (values, used) = integer::decode_prefix(reader.rest())?;
779 reader.skip(used)?;
780 Ok(values)
781}
782
783fn describe_integers(reader: &mut Reader<'_>) -> Result<String> {
784 let (text, used) = integer::describe_prefix(reader.rest())?;
785 reader.skip(used)?;
786 Ok(text)
787}
788
789pub(crate) fn sample_of<'a>(values: &[&'a [u8]]) -> Vec<&'a [u8]> {
808 sample_bytes_of(values, SAMPLE_BYTES)
809}
810
811pub(crate) fn sample_bytes_of<'a>(values: &[&'a [u8]], budget: usize) -> Vec<&'a [u8]> {
814 let budget = budget.max(1);
815 let total: usize = values.iter().map(|value| value.len()).sum();
816 if total <= budget {
817 return values.to_vec();
818 }
819 let stride = total.div_ceil(budget).max(1);
820 let span = (stride * 2 - 1).max(1) as u64;
821 let mut state = 0x2545_f491_4f6c_dd1du64;
822 let mut sample = Vec::with_capacity(values.len() / stride + 1);
823 let mut at = 0usize;
824 while at < values.len() {
825 sample.push(values[at]);
826 state ^= state << 13;
827 state ^= state >> 7;
828 state ^= state << 17;
829 at += 1 + (state % span) as usize;
830 }
831 sample
832}
833
834fn dictionary_of<'a>(values: &[&'a [u8]]) -> (Vec<&'a [u8]>, Vec<i64>) {
846 let mut order: Vec<u32> = (0..values.len() as u32).collect();
847 order.sort_unstable_by(|left, right| values[*left as usize].cmp(values[*right as usize]));
848 let mut entries: Vec<&'a [u8]> = Vec::new();
849 let mut codes = vec![0i64; values.len()];
850 for &index in &order {
851 let value = values[index as usize];
852 if entries.last() != Some(&value) {
853 entries.push(value);
854 }
855 codes[index as usize] = (entries.len() - 1) as i64;
856 }
857 (entries, codes)
858}
859
860fn has_duplicates(values: &[&[u8]]) -> bool {
870 let Some(slots) = values.len().checked_mul(2).map(usize::next_power_of_two) else {
871 return false;
872 };
873 let mask = slots - 1;
874 let mut table = vec![u32::MAX; slots];
875 for (index, value) in values.iter().enumerate() {
876 let mut at = hash_of(value) as usize & mask;
877 loop {
878 let held = table[at];
879 if held == u32::MAX {
880 table[at] = index as u32;
881 break;
882 }
883 if values[held as usize] == *value {
884 return true;
885 }
886 at = (at + 1) & mask;
887 }
888 }
889 false
890}
891
892fn hash_of(value: &[u8]) -> u64 {
899 let mut hash = 0xcbf2_9ce4_8422_2325_u64;
900 let mut chunks = value.chunks_exact(8);
901 for chunk in &mut chunks {
902 let word = u64::from_le_bytes(chunk.try_into().expect("chunks_exact(8) gives eight bytes"));
903 hash = (hash ^ word).wrapping_mul(0x1_0000_01b3);
904 }
905 for byte in chunks.remainder() {
906 hash = (hash ^ u64::from(*byte)).wrapping_mul(0x1_0000_01b3);
907 }
908 (hash ^ (value.len() as u64)).wrapping_mul(0x1_0000_01b3)
909}
910
911fn too_long(len: usize) -> Error {
912 Error::internal(format!("a string chunk of {len} is longer than the format allows"))
913}
914
915fn put_u32(out: &mut Vec<u8>, value: u32) {
916 out.extend_from_slice(&value.to_le_bytes());
917}
918
919#[cfg(test)]
920mod tests {
921 use super::*;
922
923 fn urls(count: usize) -> Vec<Vec<u8>> {
924 let hosts = ["www.example.com", "shop.example.com", "news.other.example.org"];
925 let paths = ["/index.html", "/catalog/item", "/search", "/user/profile/settings"];
926 (0..count)
927 .map(|index| {
928 let host = hosts[index % hosts.len()];
929 let path = paths[(index / 3) % paths.len()];
930 format!("http://{host}{path}?session={}&ref=google", index * 7).into_bytes()
931 })
932 .collect()
933 }
934
935 fn keyed(values: Vec<Vec<u8>>) -> Vec<Vec<u8>> {
939 values
940 .into_iter()
941 .enumerate()
942 .map(|(index, value)| {
943 let key = (index as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15) % 1_000_000_007;
944 let mut out = format!("{key:010}/").into_bytes();
945 out.extend_from_slice(&value);
946 out
947 })
948 .collect()
949 }
950
951 fn borrow(values: &[Vec<u8>]) -> Vec<&[u8]> {
952 values.iter().map(Vec::as_slice).collect()
953 }
954
955 fn round_trip(values: &[Vec<u8>]) -> Vec<u8> {
956 let borrowed = borrow(values);
957 let bytes = encode(&borrowed).unwrap();
958 let back = decode(&bytes).unwrap();
959 assert_eq!(back, values, "{}", describe(&bytes).unwrap());
960 check_flat(&bytes, values);
961 bytes
962 }
963
964 fn check_flat(bytes: &[u8], values: &[Vec<u8>]) {
967 let flat = decode_flat(bytes).unwrap();
968 let shape = describe(bytes).unwrap();
969 assert_eq!(flat.len(), values.len(), "{shape}");
970 assert_eq!(flat.iter().collect::<Vec<_>>(), borrow(values), "{shape}");
971 assert_eq!(flat.bytes(), values.concat(), "{shape}");
972 assert_eq!(flat.get(values.len()), None, "{shape}");
973 }
974
975 fn kind_of(bytes: &[u8]) -> Kind {
976 Kind::from_tag(bytes[0]).unwrap()
977 }
978
979 #[test]
980 fn every_shape_decodes_flat_to_what_it_decodes_split() {
981 let columns =
985 [urls(600), keyed(urls(600)), vec![b"same".to_vec(); 400], vec![Vec::new(); 7]];
986 for values in &columns {
987 let borrowed = borrow(values);
988 for kind in offered(&borrowed) {
989 let Some(bytes) = encode_only(kind, &borrowed).unwrap() else {
990 continue;
991 };
992 assert_eq!(decode(&bytes).unwrap(), *values, "{}", kind.name());
993 let flat = decode_flat(&bytes).unwrap();
994 assert_eq!(flat.iter().collect::<Vec<_>>(), borrowed, "{}", kind.name());
995 assert_eq!(flat.bytes(), values.concat(), "{}", kind.name());
996 }
997 }
998 }
999
1000 #[test]
1001 fn a_front_coded_chunk_that_shares_more_than_it_has_is_an_error() {
1002 let suffixes: [&[u8]; 2] = [b"abc", b"x"];
1006 let mut bytes = vec![Kind::Front.tag()];
1007 put_u32(&mut bytes, 2);
1008 bytes.extend_from_slice(&integer::encode(&[0, 9]).unwrap());
1009 bytes.extend_from_slice(&encode_only(Kind::Plain, &suffixes).unwrap().unwrap());
1010 let error = decode_flat(&bytes).expect_err("a nine byte prefix of a three byte value");
1011 assert_eq!(error.message(), "a value shares 9 bytes with a value 3 bytes long");
1012 assert_eq!(decode(&bytes).unwrap_err().message(), error.message());
1013 }
1014
1015 #[test]
1016 fn the_dictionary_is_sorted_and_the_codes_point_back_at_the_values() {
1017 let values = vec![
1020 b"pear".to_vec(),
1021 b"apple".to_vec(),
1022 b"pear".to_vec(),
1023 b"cherry".to_vec(),
1024 b"apple".to_vec(),
1025 ];
1026 let borrowed = borrow(&values);
1027 let (entries, codes) = dictionary_of(&borrowed);
1028 assert_eq!(entries, vec![b"apple".as_slice(), b"cherry".as_slice(), b"pear".as_slice()]);
1029 assert_eq!(codes, vec![2, 0, 2, 1, 0]);
1030 for (code, value) in codes.iter().zip(&borrowed) {
1031 assert_eq!(entries[*code as usize], *value);
1032 }
1033 }
1034
1035 #[test]
1036 fn a_column_with_nothing_repeated_has_no_duplicates_and_one_with_anything_does() {
1037 let distinct: Vec<Vec<u8>> =
1038 (0..5000).map(|index| format!("value-{index}").into_bytes()).collect();
1039 assert!(!has_duplicates(&borrow(&distinct)));
1040
1041 let mut repeated = distinct.clone();
1043 repeated.push(b"value-0".to_vec());
1044 assert!(has_duplicates(&borrow(&repeated)));
1045
1046 assert!(!has_duplicates(&borrow(&Vec::new())));
1047 assert!(!has_duplicates(&borrow(&[b"one".to_vec()])));
1048 assert!(has_duplicates(&borrow(&vec![b"same".to_vec(); 2])));
1049 }
1050
1051 #[test]
1052 fn long_values_that_differ_only_at_the_end_are_not_confused_for_each_other() {
1053 let stem = "http://www.example.com/a/very/long/path/that/goes/on?session=";
1056 let values: Vec<Vec<u8>> =
1057 (0..2000).map(|index| format!("{stem}{index}").into_bytes()).collect();
1058 assert!(!has_duplicates(&borrow(&values)));
1059 let (entries, codes) = dictionary_of(&borrow(&values));
1060 assert_eq!(entries.len(), values.len());
1061 assert_eq!(codes.len(), values.len());
1062 }
1063
1064 #[test]
1065 fn what_the_chooser_returns_is_the_smallest_of_what_it_was_offered() {
1066 for values in [urls(400), keyed(urls(400)), vec![b"same".to_vec(); 50], Vec::new()] {
1072 let borrowed = borrow(&values);
1073 let chosen = encode(&borrowed).unwrap();
1074 let mut smallest: Option<Vec<u8>> = None;
1075 for kind in offered(&borrowed) {
1076 let Some(bytes) = encode_only(kind, &borrowed).unwrap() else {
1077 continue;
1078 };
1079 if smallest.as_ref().is_none_or(|best| bytes.len() < best.len()) {
1080 smallest = Some(bytes);
1081 }
1082 }
1083 assert_eq!(smallest.as_deref(), Some(chosen.as_slice()), "{}", values.len());
1084 }
1085 }
1086
1087 fn raw_size(values: &[Vec<u8>]) -> usize {
1088 values.iter().map(Vec::len).sum::<usize>() + values.len() * 4
1089 }
1090
1091 #[test]
1092 fn an_empty_chunk_round_trips() {
1093 let bytes = round_trip(&[]);
1094 assert_eq!(kind_of(&bytes), Kind::Plain);
1095 }
1096
1097 #[test]
1098 fn a_constant_column_costs_what_one_value_costs() {
1099 let values = vec![b"https://www.example.com/".to_vec(); 100_000];
1100 let bytes = round_trip(&values);
1101 assert_eq!(kind_of(&bytes), Kind::Constant);
1102 assert_eq!(bytes.len(), 9 + 24);
1103 }
1104
1105 #[test]
1106 fn a_url_column_of_unique_values_is_matched_rather_than_only_compressed() {
1107 let values = keyed(urls(20_000));
1115 let bytes = round_trip(&values);
1116 assert_eq!(kind_of(&bytes), Kind::Lz);
1117
1118 let borrowed: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
1121 let fsst = encode_as(Kind::Fsst, &borrowed, 0, &EXHAUSTIVE).unwrap().unwrap();
1122 assert!(bytes.len() < fsst.len(), "{} against FSST {}", bytes.len(), fsst.len());
1123
1124 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1127 assert!(ratio > 4.0, "{ratio:.2}x");
1128 }
1129
1130 #[test]
1131 fn a_sample_of_a_periodic_column_learns_every_phase_of_it() {
1132 let values = urls(20_000);
1137 let borrowed = borrow(&values);
1138 let sample = sample_of(&borrowed);
1139 let mut phases: Vec<&[u8]> = sample
1140 .iter()
1141 .map(|value| {
1142 let query =
1143 value.iter().position(|byte| *byte == b'?').expect("every value has a query");
1144 &value[..query]
1145 })
1146 .collect();
1147 phases.sort_unstable();
1148 phases.dedup();
1149 assert_eq!(phases.len(), 12);
1151 let whole = SymbolTable::train(&borrowed);
1152 let sampled = SymbolTable::train(&sample);
1153 let mut on_whole = Vec::new();
1154 let mut on_sample = Vec::new();
1155 for value in &borrowed {
1156 whole.compress(value, &mut on_whole);
1157 sampled.compress(value, &mut on_sample);
1158 }
1159 assert!(
1162 on_sample.len() < on_whole.len() * 5 / 4,
1163 "{} against {}",
1164 on_sample.len(),
1165 on_whole.len()
1166 );
1167 }
1168
1169 #[test]
1170 fn a_repeating_column_becomes_a_dictionary_of_compressed_entries() {
1171 let distinct = urls(500);
1177 let values: Vec<Vec<u8>> =
1178 (0..50_000).map(|index| distinct[index * 7919 % distinct.len()].clone()).collect();
1179 let bytes = round_trip(&values);
1180 assert_eq!(kind_of(&bytes), Kind::Dict);
1181 let shape = describe(&bytes).unwrap();
1182 assert!(shape.starts_with("DICT(LZ("), "{shape}");
1183 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1184 assert!(ratio > 20.0, "{ratio:.2}x, {shape}");
1185 }
1186
1187 #[test]
1188 fn a_column_of_long_runs_costs_almost_nothing() {
1189 let distinct = urls(50);
1192 let mut values = Vec::new();
1193 for entry in &distinct {
1194 values.extend(std::iter::repeat_n(entry.clone(), 1000));
1195 }
1196 let bytes = round_trip(&values);
1197 let shape = describe(&bytes).unwrap();
1198 assert!(shape.contains("RLE"), "{shape}");
1199 assert!(bytes.len() < 2000, "{} bytes: {shape}", bytes.len());
1200 }
1201
1202 #[test]
1203 fn incompressible_strings_stay_close_to_their_own_size() {
1204 let mut state = 0x2545_f491_4f6c_dd1du64;
1207 let values: Vec<Vec<u8>> = (0..2000)
1208 .map(|_| {
1209 (0..32)
1210 .map(|_| {
1211 state ^= state << 13;
1212 state ^= state >> 7;
1213 state ^= state << 17;
1214 state as u8
1215 })
1216 .collect()
1217 })
1218 .collect();
1219 let bytes = round_trip(&values);
1220 assert!(bytes.len() < 2000 * 32 + 3000, "{} bytes", bytes.len());
1221 }
1222
1223 #[test]
1224 fn lengths_are_stored_rather_than_offsets() {
1225 let values: Vec<Vec<u8>> =
1228 (0..100_000).map(|index| format!("{index:024}").into_bytes()).collect();
1229 let borrowed = borrow(&values);
1230 let bytes = encode_only(Kind::Plain, &borrowed).unwrap().unwrap();
1231 assert_eq!(bytes.len(), 5 + 13 + 100_000 * 24);
1232 }
1233
1234 #[test]
1235 fn empty_strings_are_values_and_not_nulls() {
1236 let values = vec![Vec::new(), b"a".to_vec(), Vec::new(), b"bb".to_vec()];
1237 round_trip(&values);
1238 }
1239
1240 #[test]
1241 fn a_chunk_with_one_value_round_trips() {
1242 round_trip(&[b"only".to_vec()]);
1243 }
1244
1245 #[test]
1246 fn every_candidate_that_applies_decodes_to_the_input() {
1247 let values = urls(3000);
1248 let borrowed = borrow(&values);
1249 let applicable = candidates(&borrowed, 0);
1250 assert!(applicable.len() >= 2, "{applicable:?}");
1251 for kind in applicable {
1252 let bytes = encode_only(kind, &borrowed).unwrap().unwrap();
1253 assert_eq!(decode(&bytes).unwrap(), values, "{}", kind.name());
1254 }
1255 }
1256
1257 #[test]
1258 fn the_chooser_picks_the_smallest_candidate() {
1259 let values = urls(2000);
1260 let borrowed = borrow(&values);
1261 let chosen = encode(&borrowed).unwrap();
1262 for (_, size) in candidate_sizes(&borrowed).unwrap() {
1263 assert!(chosen.len() <= size);
1264 }
1265 }
1266
1267 #[test]
1268 fn a_truncated_chunk_is_an_error_and_not_a_panic() {
1269 let values = urls(40);
1270 let bytes = encode(&borrow(&values)).unwrap();
1271 for len in 0..bytes.len() {
1272 assert!(decode(&bytes[..len]).is_err(), "{len} bytes decoded");
1273 }
1274 }
1275
1276 #[test]
1277 fn trailing_bytes_are_an_error() {
1278 let mut bytes = encode(&borrow(&urls(10))).unwrap();
1279 bytes.push(0);
1280 let error = decode(&bytes).unwrap_err();
1281 assert!(error.message().contains("left over"), "{error}");
1282 }
1283
1284 #[test]
1285 fn an_unknown_tag_is_an_error() {
1286 let error = decode(&[99, 0, 0, 0, 0]).unwrap_err();
1287 assert!(error.message().contains("unknown string encoding tag"), "{error}");
1288 }
1289
1290 #[test]
1291 fn a_dictionary_code_outside_the_dictionary_is_an_error() {
1292 let mut bytes = vec![Kind::Dict.tag()];
1293 put_u32(&mut bytes, 1);
1294 bytes.extend_from_slice(&encode(&[b"one".as_slice()]).unwrap());
1295 bytes.extend_from_slice(&integer::encode(&[9]).unwrap());
1296 let error = decode(&bytes).unwrap_err();
1297 assert!(error.message().contains("not in the dictionary"), "{error}");
1298 }
1299
1300 #[test]
1301 fn a_sorted_column_of_urls_is_front_coded() {
1302 let mut values = urls(20_000);
1306 values.sort();
1307 let bytes = round_trip(&values);
1308 assert_eq!(kind_of(&bytes), Kind::Front);
1309 let shape = describe(&bytes).unwrap();
1310 let mut plain = Vec::new();
1311 let borrowed = borrow(&values);
1312 for (kind, size) in candidate_sizes(&borrowed).unwrap() {
1313 if kind == Kind::Fsst {
1314 plain.push(size);
1315 }
1316 }
1317 let fsst = plain[0];
1318 assert!(bytes.len() * 2 < fsst, "{} against FSST {fsst}: {shape}", bytes.len());
1319 }
1320
1321 #[test]
1322 fn a_column_with_nothing_to_share_is_not_offered_front_coding() {
1323 let mut state = 0x9e37_79b9_7f4a_7c15u64;
1326 let values: Vec<Vec<u8>> = (0..2000)
1327 .map(|_| {
1328 (0..24)
1329 .map(|_| {
1330 state ^= state << 13;
1331 state ^= state >> 7;
1332 state ^= state << 17;
1333 (state % 251) as u8
1334 })
1335 .collect()
1336 })
1337 .collect();
1338 let borrowed = borrow(&values);
1339 assert!(!candidates(&borrowed, 0).contains(&Kind::Front));
1340 }
1341
1342 #[test]
1343 fn a_prefix_longer_than_the_value_before_it_is_an_error() {
1344 let mut bytes = vec![Kind::Front.tag()];
1345 put_u32(&mut bytes, 2);
1346 bytes.extend_from_slice(&integer::encode(&[0, 9]).unwrap());
1347 bytes.extend_from_slice(&encode(&[b"one".as_slice(), b"two".as_slice()]).unwrap());
1348 let error = decode(&bytes).unwrap_err();
1349 assert!(error.message().contains("shares 9 bytes"), "{error}");
1350 }
1351
1352 #[test]
1353 fn a_negative_prefix_is_an_error() {
1354 let mut bytes = vec![Kind::Front.tag()];
1355 put_u32(&mut bytes, 1);
1356 bytes.extend_from_slice(&integer::encode(&[-1]).unwrap());
1357 bytes.extend_from_slice(&encode(&[b"one".as_slice()]).unwrap());
1358 let error = decode(&bytes).unwrap_err();
1359 assert!(error.message().contains("negative shared prefix"), "{error}");
1360 }
1361
1362 #[test]
1363 fn a_negative_length_is_an_error() {
1364 let mut bytes = vec![Kind::Plain.tag()];
1365 put_u32(&mut bytes, 1);
1366 bytes.extend_from_slice(&integer::encode(&[-1]).unwrap());
1367 let error = decode(&bytes).unwrap_err();
1368 assert!(error.message().contains("negative string length"), "{error}");
1369 }
1370
1371 #[test]
1372 fn the_sample_is_spread_across_the_chunk_and_not_taken_from_the_front() {
1373 let mut values: Vec<Vec<u8>> = Vec::new();
1376 for index in 0..20_000 {
1377 let head = if index < 10_000 { "aaaaaaaaaaaaaaaa" } else { "zzzzzzzzzzzzzzzz" };
1378 values.push(format!("{head}/{index:08}").into_bytes());
1379 }
1380 let borrowed = borrow(&values);
1381 let sample = sample_of(&borrowed);
1382 let first_half = sample.iter().filter(|value| value.starts_with(b"aaaa")).count();
1383 let second_half = sample.len() - first_half;
1384 assert!(first_half > 0 && second_half > 0, "{first_half} and {second_half}");
1385 let bytes = round_trip(&values);
1386 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1387 assert!(ratio > 4.0, "{ratio:.2}x");
1388 }
1389}