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
171pub fn decode_prefix(bytes: &[u8]) -> Result<(Vec<Vec<u8>>, usize)> {
180 let mut reader = Reader::new(bytes);
181 let values = decode_chunk(&mut reader)?;
182 Ok((values, reader.used()))
183}
184
185pub fn describe_prefix(bytes: &[u8]) -> Result<(String, usize)> {
191 let mut reader = Reader::new(bytes);
192 let text = describe_chunk(&mut reader)?;
193 Ok((text, reader.used()))
194}
195
196pub fn decode(bytes: &[u8]) -> Result<Vec<Vec<u8>>> {
202 let mut reader = Reader::new(bytes);
203 let values = decode_chunk(&mut reader)?;
204 if reader.remaining() != 0 {
205 return Err(Error::internal(format!(
206 "{} bytes left over after decoding a string chunk",
207 reader.remaining()
208 )));
209 }
210 Ok(values)
211}
212
213pub fn candidate_sizes(values: &[&[u8]]) -> Result<Vec<(Kind, usize)>> {
220 let mut sizes = Vec::new();
221 for kind in candidates(values, 0) {
222 if let Some(bytes) = encode_as(kind, values, 0, &EXHAUSTIVE)? {
223 sizes.push((kind, bytes.len()));
224 }
225 }
226 Ok(sizes)
227}
228
229#[must_use]
236pub fn offered(values: &[&[u8]]) -> Vec<Kind> {
237 candidates(values, 0)
238}
239
240pub fn encode_only(kind: Kind, values: &[&[u8]]) -> Result<Option<Vec<u8>>> {
252 encode_as(kind, values, 0, &EXHAUSTIVE)
253}
254
255pub(crate) fn size_as(kind: Kind, values: &[&[u8]], depth: u8) -> Result<Option<usize>> {
260 Ok(encode_as(kind, values, depth, &EXHAUSTIVE)?.map(|bytes| bytes.len()))
261}
262
263pub fn describe(bytes: &[u8]) -> Result<String> {
269 let mut reader = Reader::new(bytes);
270 describe_chunk(&mut reader)
271}
272
273fn encode_at(values: &[&[u8]], depth: u8, chooser: &dyn Chooser) -> Result<Vec<u8>> {
274 let offered = candidates(values, depth);
275 let mut best: Option<Vec<u8>> = None;
276 for kind in chooser.narrow_strings(values, &offered, depth) {
277 let Some(bytes) = encode_as(kind, values, depth, chooser)? else {
278 continue;
279 };
280 if best.as_ref().is_none_or(|current| bytes.len() < current.len()) {
281 best = Some(bytes);
282 }
283 }
284 best.ok_or_else(|| Error::internal("no string encoding applied to the chunk"))
285}
286
287fn candidates(values: &[&[u8]], depth: u8) -> Vec<Kind> {
288 let mut kinds = vec![Kind::Plain];
289 if values.is_empty() {
290 return kinds;
291 }
292 if values.iter().all(|value| *value == values[0]) {
293 return vec![Kind::Constant];
294 }
295 kinds.push(Kind::Fsst);
296 if depth < MAX_DEPTH && has_duplicates(values) {
297 kinds.push(Kind::Dict);
298 }
299 if depth < MAX_DEPTH && sharing_of(values) >= total_len(values) / SHARE_DIVISOR {
300 kinds.push(Kind::Front);
301 }
302 if depth < MAX_DEPTH && total_len(values) >= LZ_FLOOR {
303 kinds.push(Kind::Lz);
304 }
305 kinds
306}
307
308fn sharing_of(values: &[&[u8]]) -> usize {
315 let mut shared = 0;
316 for pair in values.windows(2) {
317 shared += shared_prefix(pair[0], pair[1]);
318 }
319 shared
320}
321
322pub(crate) fn front_code<'a>(values: &[&'a [u8]]) -> (Vec<i64>, Vec<&'a [u8]>) {
328 let mut prefixes = Vec::with_capacity(values.len());
329 let mut suffixes: Vec<&'a [u8]> = Vec::with_capacity(values.len());
330 let mut previous: &[u8] = b"";
331 for value in values {
332 let value: &'a [u8] = value;
333 let shared = shared_prefix(previous, value);
334 prefixes.push(shared as i64);
335 suffixes.push(&value[shared..]);
336 previous = value;
337 }
338 (prefixes, suffixes)
339}
340
341pub(crate) fn front_decode(prefixes: &[i64], suffixes: Vec<Vec<u8>>) -> Result<Vec<Vec<u8>>> {
348 let mut values: Vec<Vec<u8>> = Vec::with_capacity(suffixes.len());
349 for (index, suffix) in suffixes.into_iter().enumerate() {
350 let shared = usize::try_from(prefixes[index])
351 .map_err(|_| Error::internal("a negative shared prefix length"))?;
352 let previous: &[u8] = if index == 0 { b"" } else { &values[index - 1] };
353 if shared > previous.len() {
354 return Err(Error::internal(format!(
355 "a value shares {shared} bytes with a value {} bytes long",
356 previous.len()
357 )));
358 }
359 let mut value = Vec::with_capacity(shared + suffix.len());
360 value.extend_from_slice(&previous[..shared]);
361 value.extend_from_slice(&suffix);
362 values.push(value);
363 }
364 Ok(values)
365}
366
367fn shared_prefix(previous: &[u8], value: &[u8]) -> usize {
368 let limit = previous.len().min(value.len());
369 let mut shared = 0;
370 while shared < limit && previous[shared] == value[shared] {
371 shared += 1;
372 }
373 shared
374}
375
376fn total_len(values: &[&[u8]]) -> usize {
377 values.iter().map(|value| value.len()).sum()
378}
379
380fn encode_as(
381 kind: Kind,
382 values: &[&[u8]],
383 depth: u8,
384 chooser: &dyn Chooser,
385) -> Result<Option<Vec<u8>>> {
386 let mut out = vec![kind.tag()];
387 put_u32(&mut out, u32::try_from(values.len()).map_err(|_| too_long(values.len()))?);
388 match kind {
389 Kind::Constant => {
390 let Some(first) = values.first() else {
391 return Ok(None);
392 };
393 if values.iter().any(|value| value != first) {
394 return Ok(None);
395 }
396 put_u32(&mut out, u32::try_from(first.len()).map_err(|_| too_long(first.len()))?);
397 out.extend_from_slice(first);
398 }
399 Kind::Plain => {
400 out.extend_from_slice(&encode_lengths(values, chooser)?);
401 for value in values {
402 out.extend_from_slice(value);
403 }
404 }
405 Kind::Fsst => {
406 let sample = sample_of(values);
407 let table = SymbolTable::train(&sample);
408 if table.is_empty() {
409 return Ok(None);
410 }
411 let mut compressed = Vec::new();
412 let mut lengths = Vec::with_capacity(values.len());
413 for value in values {
414 let before = compressed.len();
415 table.compress(value, &mut compressed);
416 lengths.push((compressed.len() - before) as i64);
417 }
418 table.serialize(&mut out);
419 out.extend_from_slice(&integer::encode_with(&lengths, chooser)?);
420 out.extend_from_slice(&compressed);
421 }
422 Kind::Dict => {
423 let (entries, codes) = dictionary_of(values);
424 if entries.is_empty() {
425 return Ok(None);
426 }
427 out.extend_from_slice(&encode_at(&entries, depth + 1, chooser)?);
428 out.extend_from_slice(&integer::encode_with(&codes, chooser)?);
429 }
430 Kind::Front => {
431 let (prefixes, suffixes) = front_code(values);
432 out.extend_from_slice(&integer::encode_with(&prefixes, chooser)?);
433 out.extend_from_slice(&encode_at(&suffixes, depth + 1, chooser)?);
434 }
435 Kind::Lz => {
436 let mut joined = Vec::with_capacity(total_len(values));
437 let mut sizes = Vec::with_capacity(values.len());
438 for value in values {
439 joined.extend_from_slice(value);
440 sizes.push(value.len() as i64);
441 }
442 let tokens = lz::tokens_of(&joined);
443 out.extend_from_slice(&integer::encode_with(&sizes, chooser)?);
444 out.extend_from_slice(&integer::encode_with(&tokens.lengths, chooser)?);
445 out.extend_from_slice(&integer::encode_with(&tokens.offsets, chooser)?);
446 out.extend_from_slice(&encode_at(&tokens.literals, depth + 1, chooser)?);
447 }
448 }
449 Ok(Some(out))
450}
451
452fn decode_chunk(reader: &mut Reader<'_>) -> Result<Vec<Vec<u8>>> {
453 let kind = Kind::from_tag(reader.u8()?)?;
454 let count = reader.u32()? as usize;
455 match kind {
456 Kind::Constant => {
457 let len = reader.u32()? as usize;
458 let value = reader.bytes(len)?.to_vec();
459 Ok(vec![value; count])
460 }
461 Kind::Plain => {
462 let lengths = decode_lengths(reader, count)?;
463 let mut values = Vec::with_capacity(count);
464 for length in lengths {
465 values.push(reader.bytes(length)?.to_vec());
466 }
467 Ok(values)
468 }
469 Kind::Fsst => {
470 let (table, used) = SymbolTable::deserialize(reader.rest())?;
471 reader.skip(used)?;
472 let lengths = decode_lengths(reader, count)?;
473 let mut values = Vec::with_capacity(count);
474 for length in lengths {
475 let compressed = reader.bytes(length)?;
476 let mut value = Vec::new();
477 table.decompress(compressed, &mut value)?;
478 values.push(value);
479 }
480 Ok(values)
481 }
482 Kind::Dict => {
483 let dictionary = decode_chunk(reader)?;
484 let codes = decode_integers(reader)?;
485 if codes.len() != count {
486 return Err(Error::internal(format!(
487 "a dictionary chunk says it holds {count} values and has {} codes",
488 codes.len()
489 )));
490 }
491 let mut values = Vec::with_capacity(count);
492 for code in codes {
493 let entry =
494 usize::try_from(code).ok().and_then(|index| dictionary.get(index)).ok_or_else(
495 || Error::internal(format!("code {code} is not in the dictionary")),
496 )?;
497 values.push(entry.clone());
498 }
499 Ok(values)
500 }
501 Kind::Front => {
502 let prefixes = decode_integers(reader)?;
503 let suffixes = decode_chunk(reader)?;
504 if prefixes.len() != count || suffixes.len() != count {
505 return Err(Error::internal(format!(
506 "a front coded chunk says it holds {count} values and has {} prefixes and {} suffixes",
507 prefixes.len(),
508 suffixes.len()
509 )));
510 }
511 front_decode(&prefixes, suffixes)
512 }
513 Kind::Lz => {
514 let sizes = decode_integers(reader)?;
515 let lengths = decode_integers(reader)?;
516 let offsets = decode_integers(reader)?;
517 let literals = decode_chunk(reader)?;
518 if sizes.len() != count {
519 return Err(Error::internal(format!(
520 "a matched chunk says it holds {count} values and has {} lengths",
521 sizes.len()
522 )));
523 }
524 let mut total = 0usize;
525 let mut widths = Vec::with_capacity(count);
526 for size in sizes {
527 let width = usize::try_from(size)
528 .map_err(|_| Error::internal("a negative string length"))?;
529 total += width;
530 widths.push(width);
531 }
532 let joined = lz::rebuild(&literals, &lengths, &offsets, total)?;
533 if joined.len() != total {
534 return Err(Error::internal(format!(
535 "a matched chunk rebuilt {} bytes where its lengths add up to {total}",
536 joined.len()
537 )));
538 }
539 let mut values = Vec::with_capacity(count);
540 let mut at = 0;
541 for width in widths {
542 values.push(joined[at..at + width].to_vec());
543 at += width;
544 }
545 Ok(values)
546 }
547 }
548}
549
550fn describe_chunk(reader: &mut Reader<'_>) -> Result<String> {
551 let kind = Kind::from_tag(reader.u8()?)?;
552 let count = reader.u32()? as usize;
553 Ok(match kind {
554 Kind::Constant => {
555 let len = reader.u32()? as usize;
556 reader.bytes(len)?;
557 "CONSTANT".to_string()
558 }
559 Kind::Plain => {
560 let (shape, lengths) = describe_lengths(reader, count)?;
561 reader.skip(lengths.iter().sum())?;
562 format!("PLAIN({shape})")
563 }
564 Kind::Fsst => {
565 let (table, used) = SymbolTable::deserialize(reader.rest())?;
566 reader.skip(used)?;
567 let (shape, lengths) = describe_lengths(reader, count)?;
568 reader.skip(lengths.iter().sum())?;
569 format!("FSST[{}]({shape})", table.len())
570 }
571 Kind::Dict => {
572 let entries = describe_chunk(reader)?;
573 let codes = describe_integers(reader)?;
574 format!("DICT({entries}, {codes})")
575 }
576 Kind::Front => {
577 let prefixes = describe_integers(reader)?;
578 let suffixes = describe_chunk(reader)?;
579 format!("FRONT({prefixes}, {suffixes})")
580 }
581 Kind::Lz => {
582 let sizes = describe_integers(reader)?;
583 let lengths = describe_integers(reader)?;
584 let offsets = describe_integers(reader)?;
585 let literals = describe_chunk(reader)?;
586 format!("LZ({sizes}, {lengths}, {offsets}, {literals})")
587 }
588 })
589}
590
591fn describe_lengths(reader: &mut Reader<'_>, count: usize) -> Result<(String, Vec<usize>)> {
595 let (shape, _) = integer::describe_prefix(reader.rest())?;
596 let lengths = decode_lengths(reader, count)?;
597 Ok((shape, lengths))
598}
599
600fn encode_lengths(values: &[&[u8]], chooser: &dyn Chooser) -> Result<Vec<u8>> {
601 let lengths: Vec<i64> = values.iter().map(|value| value.len() as i64).collect();
602 integer::encode_with(&lengths, chooser)
603}
604
605fn decode_lengths(reader: &mut Reader<'_>, count: usize) -> Result<Vec<usize>> {
606 let lengths = decode_integers(reader)?;
607 if lengths.len() != count {
608 return Err(Error::internal(format!(
609 "a string chunk says it holds {count} values and has {} lengths",
610 lengths.len()
611 )));
612 }
613 lengths
614 .into_iter()
615 .map(|length| {
616 usize::try_from(length).map_err(|_| Error::internal("a negative string length"))
617 })
618 .collect()
619}
620
621fn decode_integers(reader: &mut Reader<'_>) -> Result<Vec<i64>> {
625 let (values, used) = integer::decode_prefix(reader.rest())?;
626 reader.skip(used)?;
627 Ok(values)
628}
629
630fn describe_integers(reader: &mut Reader<'_>) -> Result<String> {
631 let (text, used) = integer::describe_prefix(reader.rest())?;
632 reader.skip(used)?;
633 Ok(text)
634}
635
636pub(crate) fn sample_of<'a>(values: &[&'a [u8]]) -> Vec<&'a [u8]> {
655 sample_bytes_of(values, SAMPLE_BYTES)
656}
657
658pub(crate) fn sample_bytes_of<'a>(values: &[&'a [u8]], budget: usize) -> Vec<&'a [u8]> {
661 let budget = budget.max(1);
662 let total: usize = values.iter().map(|value| value.len()).sum();
663 if total <= budget {
664 return values.to_vec();
665 }
666 let stride = total.div_ceil(budget).max(1);
667 let span = (stride * 2 - 1).max(1) as u64;
668 let mut state = 0x2545_f491_4f6c_dd1du64;
669 let mut sample = Vec::with_capacity(values.len() / stride + 1);
670 let mut at = 0usize;
671 while at < values.len() {
672 sample.push(values[at]);
673 state ^= state << 13;
674 state ^= state >> 7;
675 state ^= state << 17;
676 at += 1 + (state % span) as usize;
677 }
678 sample
679}
680
681fn dictionary_of<'a>(values: &[&'a [u8]]) -> (Vec<&'a [u8]>, Vec<i64>) {
693 let mut order: Vec<u32> = (0..values.len() as u32).collect();
694 order.sort_unstable_by(|left, right| values[*left as usize].cmp(values[*right as usize]));
695 let mut entries: Vec<&'a [u8]> = Vec::new();
696 let mut codes = vec![0i64; values.len()];
697 for &index in &order {
698 let value = values[index as usize];
699 if entries.last() != Some(&value) {
700 entries.push(value);
701 }
702 codes[index as usize] = (entries.len() - 1) as i64;
703 }
704 (entries, codes)
705}
706
707fn has_duplicates(values: &[&[u8]]) -> bool {
717 let Some(slots) = values.len().checked_mul(2).map(usize::next_power_of_two) else {
718 return false;
719 };
720 let mask = slots - 1;
721 let mut table = vec![u32::MAX; slots];
722 for (index, value) in values.iter().enumerate() {
723 let mut at = hash_of(value) as usize & mask;
724 loop {
725 let held = table[at];
726 if held == u32::MAX {
727 table[at] = index as u32;
728 break;
729 }
730 if values[held as usize] == *value {
731 return true;
732 }
733 at = (at + 1) & mask;
734 }
735 }
736 false
737}
738
739fn hash_of(value: &[u8]) -> u64 {
746 let mut hash = 0xcbf2_9ce4_8422_2325_u64;
747 let mut chunks = value.chunks_exact(8);
748 for chunk in &mut chunks {
749 let word = u64::from_le_bytes(chunk.try_into().expect("chunks_exact(8) gives eight bytes"));
750 hash = (hash ^ word).wrapping_mul(0x1_0000_01b3);
751 }
752 for byte in chunks.remainder() {
753 hash = (hash ^ u64::from(*byte)).wrapping_mul(0x1_0000_01b3);
754 }
755 (hash ^ (value.len() as u64)).wrapping_mul(0x1_0000_01b3)
756}
757
758fn too_long(len: usize) -> Error {
759 Error::internal(format!("a string chunk of {len} is longer than the format allows"))
760}
761
762fn put_u32(out: &mut Vec<u8>, value: u32) {
763 out.extend_from_slice(&value.to_le_bytes());
764}
765
766#[cfg(test)]
767mod tests {
768 use super::*;
769
770 fn urls(count: usize) -> Vec<Vec<u8>> {
771 let hosts = ["www.example.com", "shop.example.com", "news.other.example.org"];
772 let paths = ["/index.html", "/catalog/item", "/search", "/user/profile/settings"];
773 (0..count)
774 .map(|index| {
775 let host = hosts[index % hosts.len()];
776 let path = paths[(index / 3) % paths.len()];
777 format!("http://{host}{path}?session={}&ref=google", index * 7).into_bytes()
778 })
779 .collect()
780 }
781
782 fn keyed(values: Vec<Vec<u8>>) -> Vec<Vec<u8>> {
786 values
787 .into_iter()
788 .enumerate()
789 .map(|(index, value)| {
790 let key = (index as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15) % 1_000_000_007;
791 let mut out = format!("{key:010}/").into_bytes();
792 out.extend_from_slice(&value);
793 out
794 })
795 .collect()
796 }
797
798 fn borrow(values: &[Vec<u8>]) -> Vec<&[u8]> {
799 values.iter().map(Vec::as_slice).collect()
800 }
801
802 fn round_trip(values: &[Vec<u8>]) -> Vec<u8> {
803 let borrowed = borrow(values);
804 let bytes = encode(&borrowed).unwrap();
805 let back = decode(&bytes).unwrap();
806 assert_eq!(back, values, "{}", describe(&bytes).unwrap());
807 bytes
808 }
809
810 fn kind_of(bytes: &[u8]) -> Kind {
811 Kind::from_tag(bytes[0]).unwrap()
812 }
813
814 #[test]
815 fn the_dictionary_is_sorted_and_the_codes_point_back_at_the_values() {
816 let values = vec![
819 b"pear".to_vec(),
820 b"apple".to_vec(),
821 b"pear".to_vec(),
822 b"cherry".to_vec(),
823 b"apple".to_vec(),
824 ];
825 let borrowed = borrow(&values);
826 let (entries, codes) = dictionary_of(&borrowed);
827 assert_eq!(entries, vec![b"apple".as_slice(), b"cherry".as_slice(), b"pear".as_slice()]);
828 assert_eq!(codes, vec![2, 0, 2, 1, 0]);
829 for (code, value) in codes.iter().zip(&borrowed) {
830 assert_eq!(entries[*code as usize], *value);
831 }
832 }
833
834 #[test]
835 fn a_column_with_nothing_repeated_has_no_duplicates_and_one_with_anything_does() {
836 let distinct: Vec<Vec<u8>> =
837 (0..5000).map(|index| format!("value-{index}").into_bytes()).collect();
838 assert!(!has_duplicates(&borrow(&distinct)));
839
840 let mut repeated = distinct.clone();
842 repeated.push(b"value-0".to_vec());
843 assert!(has_duplicates(&borrow(&repeated)));
844
845 assert!(!has_duplicates(&borrow(&Vec::new())));
846 assert!(!has_duplicates(&borrow(&[b"one".to_vec()])));
847 assert!(has_duplicates(&borrow(&vec![b"same".to_vec(); 2])));
848 }
849
850 #[test]
851 fn long_values_that_differ_only_at_the_end_are_not_confused_for_each_other() {
852 let stem = "http://www.example.com/a/very/long/path/that/goes/on?session=";
855 let values: Vec<Vec<u8>> =
856 (0..2000).map(|index| format!("{stem}{index}").into_bytes()).collect();
857 assert!(!has_duplicates(&borrow(&values)));
858 let (entries, codes) = dictionary_of(&borrow(&values));
859 assert_eq!(entries.len(), values.len());
860 assert_eq!(codes.len(), values.len());
861 }
862
863 #[test]
864 fn what_the_chooser_returns_is_the_smallest_of_what_it_was_offered() {
865 for values in [urls(400), keyed(urls(400)), vec![b"same".to_vec(); 50], Vec::new()] {
871 let borrowed = borrow(&values);
872 let chosen = encode(&borrowed).unwrap();
873 let mut smallest: Option<Vec<u8>> = None;
874 for kind in offered(&borrowed) {
875 let Some(bytes) = encode_only(kind, &borrowed).unwrap() else {
876 continue;
877 };
878 if smallest.as_ref().is_none_or(|best| bytes.len() < best.len()) {
879 smallest = Some(bytes);
880 }
881 }
882 assert_eq!(smallest.as_deref(), Some(chosen.as_slice()), "{}", values.len());
883 }
884 }
885
886 fn raw_size(values: &[Vec<u8>]) -> usize {
887 values.iter().map(Vec::len).sum::<usize>() + values.len() * 4
888 }
889
890 #[test]
891 fn an_empty_chunk_round_trips() {
892 let bytes = round_trip(&[]);
893 assert_eq!(kind_of(&bytes), Kind::Plain);
894 }
895
896 #[test]
897 fn a_constant_column_costs_what_one_value_costs() {
898 let values = vec![b"https://www.example.com/".to_vec(); 100_000];
899 let bytes = round_trip(&values);
900 assert_eq!(kind_of(&bytes), Kind::Constant);
901 assert_eq!(bytes.len(), 9 + 24);
902 }
903
904 #[test]
905 fn a_url_column_of_unique_values_is_matched_rather_than_only_compressed() {
906 let values = keyed(urls(20_000));
914 let bytes = round_trip(&values);
915 assert_eq!(kind_of(&bytes), Kind::Lz);
916
917 let borrowed: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
920 let fsst = encode_as(Kind::Fsst, &borrowed, 0, &EXHAUSTIVE).unwrap().unwrap();
921 assert!(bytes.len() < fsst.len(), "{} against FSST {}", bytes.len(), fsst.len());
922
923 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
926 assert!(ratio > 4.0, "{ratio:.2}x");
927 }
928
929 #[test]
930 fn a_sample_of_a_periodic_column_learns_every_phase_of_it() {
931 let values = urls(20_000);
936 let borrowed = borrow(&values);
937 let sample = sample_of(&borrowed);
938 let mut phases: Vec<&[u8]> = sample
939 .iter()
940 .map(|value| {
941 let query =
942 value.iter().position(|byte| *byte == b'?').expect("every value has a query");
943 &value[..query]
944 })
945 .collect();
946 phases.sort_unstable();
947 phases.dedup();
948 assert_eq!(phases.len(), 12);
950 let whole = SymbolTable::train(&borrowed);
951 let sampled = SymbolTable::train(&sample);
952 let mut on_whole = Vec::new();
953 let mut on_sample = Vec::new();
954 for value in &borrowed {
955 whole.compress(value, &mut on_whole);
956 sampled.compress(value, &mut on_sample);
957 }
958 assert!(
961 on_sample.len() < on_whole.len() * 5 / 4,
962 "{} against {}",
963 on_sample.len(),
964 on_whole.len()
965 );
966 }
967
968 #[test]
969 fn a_repeating_column_becomes_a_dictionary_of_compressed_entries() {
970 let distinct = urls(500);
976 let values: Vec<Vec<u8>> =
977 (0..50_000).map(|index| distinct[index * 7919 % distinct.len()].clone()).collect();
978 let bytes = round_trip(&values);
979 assert_eq!(kind_of(&bytes), Kind::Dict);
980 let shape = describe(&bytes).unwrap();
981 assert!(shape.starts_with("DICT(LZ("), "{shape}");
982 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
983 assert!(ratio > 20.0, "{ratio:.2}x, {shape}");
984 }
985
986 #[test]
987 fn a_column_of_long_runs_costs_almost_nothing() {
988 let distinct = urls(50);
991 let mut values = Vec::new();
992 for entry in &distinct {
993 values.extend(std::iter::repeat_n(entry.clone(), 1000));
994 }
995 let bytes = round_trip(&values);
996 let shape = describe(&bytes).unwrap();
997 assert!(shape.contains("RLE"), "{shape}");
998 assert!(bytes.len() < 2000, "{} bytes: {shape}", bytes.len());
999 }
1000
1001 #[test]
1002 fn incompressible_strings_stay_close_to_their_own_size() {
1003 let mut state = 0x2545_f491_4f6c_dd1du64;
1006 let values: Vec<Vec<u8>> = (0..2000)
1007 .map(|_| {
1008 (0..32)
1009 .map(|_| {
1010 state ^= state << 13;
1011 state ^= state >> 7;
1012 state ^= state << 17;
1013 state as u8
1014 })
1015 .collect()
1016 })
1017 .collect();
1018 let bytes = round_trip(&values);
1019 assert!(bytes.len() < 2000 * 32 + 3000, "{} bytes", bytes.len());
1020 }
1021
1022 #[test]
1023 fn lengths_are_stored_rather_than_offsets() {
1024 let values: Vec<Vec<u8>> =
1027 (0..100_000).map(|index| format!("{index:024}").into_bytes()).collect();
1028 let borrowed = borrow(&values);
1029 let bytes = encode_only(Kind::Plain, &borrowed).unwrap().unwrap();
1030 assert_eq!(bytes.len(), 5 + 13 + 100_000 * 24);
1031 }
1032
1033 #[test]
1034 fn empty_strings_are_values_and_not_nulls() {
1035 let values = vec![Vec::new(), b"a".to_vec(), Vec::new(), b"bb".to_vec()];
1036 round_trip(&values);
1037 }
1038
1039 #[test]
1040 fn a_chunk_with_one_value_round_trips() {
1041 round_trip(&[b"only".to_vec()]);
1042 }
1043
1044 #[test]
1045 fn every_candidate_that_applies_decodes_to_the_input() {
1046 let values = urls(3000);
1047 let borrowed = borrow(&values);
1048 let applicable = candidates(&borrowed, 0);
1049 assert!(applicable.len() >= 2, "{applicable:?}");
1050 for kind in applicable {
1051 let bytes = encode_only(kind, &borrowed).unwrap().unwrap();
1052 assert_eq!(decode(&bytes).unwrap(), values, "{}", kind.name());
1053 }
1054 }
1055
1056 #[test]
1057 fn the_chooser_picks_the_smallest_candidate() {
1058 let values = urls(2000);
1059 let borrowed = borrow(&values);
1060 let chosen = encode(&borrowed).unwrap();
1061 for (_, size) in candidate_sizes(&borrowed).unwrap() {
1062 assert!(chosen.len() <= size);
1063 }
1064 }
1065
1066 #[test]
1067 fn a_truncated_chunk_is_an_error_and_not_a_panic() {
1068 let values = urls(40);
1069 let bytes = encode(&borrow(&values)).unwrap();
1070 for len in 0..bytes.len() {
1071 assert!(decode(&bytes[..len]).is_err(), "{len} bytes decoded");
1072 }
1073 }
1074
1075 #[test]
1076 fn trailing_bytes_are_an_error() {
1077 let mut bytes = encode(&borrow(&urls(10))).unwrap();
1078 bytes.push(0);
1079 let error = decode(&bytes).unwrap_err();
1080 assert!(error.message().contains("left over"), "{error}");
1081 }
1082
1083 #[test]
1084 fn an_unknown_tag_is_an_error() {
1085 let error = decode(&[99, 0, 0, 0, 0]).unwrap_err();
1086 assert!(error.message().contains("unknown string encoding tag"), "{error}");
1087 }
1088
1089 #[test]
1090 fn a_dictionary_code_outside_the_dictionary_is_an_error() {
1091 let mut bytes = vec![Kind::Dict.tag()];
1092 put_u32(&mut bytes, 1);
1093 bytes.extend_from_slice(&encode(&[b"one".as_slice()]).unwrap());
1094 bytes.extend_from_slice(&integer::encode(&[9]).unwrap());
1095 let error = decode(&bytes).unwrap_err();
1096 assert!(error.message().contains("not in the dictionary"), "{error}");
1097 }
1098
1099 #[test]
1100 fn a_sorted_column_of_urls_is_front_coded() {
1101 let mut values = urls(20_000);
1105 values.sort();
1106 let bytes = round_trip(&values);
1107 assert_eq!(kind_of(&bytes), Kind::Front);
1108 let shape = describe(&bytes).unwrap();
1109 let mut plain = Vec::new();
1110 let borrowed = borrow(&values);
1111 for (kind, size) in candidate_sizes(&borrowed).unwrap() {
1112 if kind == Kind::Fsst {
1113 plain.push(size);
1114 }
1115 }
1116 let fsst = plain[0];
1117 assert!(bytes.len() * 2 < fsst, "{} against FSST {fsst}: {shape}", bytes.len());
1118 }
1119
1120 #[test]
1121 fn a_column_with_nothing_to_share_is_not_offered_front_coding() {
1122 let mut state = 0x9e37_79b9_7f4a_7c15u64;
1125 let values: Vec<Vec<u8>> = (0..2000)
1126 .map(|_| {
1127 (0..24)
1128 .map(|_| {
1129 state ^= state << 13;
1130 state ^= state >> 7;
1131 state ^= state << 17;
1132 (state % 251) as u8
1133 })
1134 .collect()
1135 })
1136 .collect();
1137 let borrowed = borrow(&values);
1138 assert!(!candidates(&borrowed, 0).contains(&Kind::Front));
1139 }
1140
1141 #[test]
1142 fn a_prefix_longer_than_the_value_before_it_is_an_error() {
1143 let mut bytes = vec![Kind::Front.tag()];
1144 put_u32(&mut bytes, 2);
1145 bytes.extend_from_slice(&integer::encode(&[0, 9]).unwrap());
1146 bytes.extend_from_slice(&encode(&[b"one".as_slice(), b"two".as_slice()]).unwrap());
1147 let error = decode(&bytes).unwrap_err();
1148 assert!(error.message().contains("shares 9 bytes"), "{error}");
1149 }
1150
1151 #[test]
1152 fn a_negative_prefix_is_an_error() {
1153 let mut bytes = vec![Kind::Front.tag()];
1154 put_u32(&mut bytes, 1);
1155 bytes.extend_from_slice(&integer::encode(&[-1]).unwrap());
1156 bytes.extend_from_slice(&encode(&[b"one".as_slice()]).unwrap());
1157 let error = decode(&bytes).unwrap_err();
1158 assert!(error.message().contains("negative shared prefix"), "{error}");
1159 }
1160
1161 #[test]
1162 fn a_negative_length_is_an_error() {
1163 let mut bytes = vec![Kind::Plain.tag()];
1164 put_u32(&mut bytes, 1);
1165 bytes.extend_from_slice(&integer::encode(&[-1]).unwrap());
1166 let error = decode(&bytes).unwrap_err();
1167 assert!(error.message().contains("negative string length"), "{error}");
1168 }
1169
1170 #[test]
1171 fn the_sample_is_spread_across_the_chunk_and_not_taken_from_the_front() {
1172 let mut values: Vec<Vec<u8>> = Vec::new();
1175 for index in 0..20_000 {
1176 let head = if index < 10_000 { "aaaaaaaaaaaaaaaa" } else { "zzzzzzzzzzzzzzzz" };
1177 values.push(format!("{head}/{index:08}").into_bytes());
1178 }
1179 let borrowed = borrow(&values);
1180 let sample = sample_of(&borrowed);
1181 let first_half = sample.iter().filter(|value| value.starts_with(b"aaaa")).count();
1182 let second_half = sample.len() - first_half;
1183 assert!(first_half > 0 && second_half > 0, "{first_half} and {second_half}");
1184 let bytes = round_trip(&values);
1185 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1186 assert!(ratio > 4.0, "{ratio:.2}x");
1187 }
1188}