1use rudb_common::{Error, Result};
65
66use crate::chooser::{Chooser, EXHAUSTIVE};
67use crate::fsst::SymbolTable;
68use crate::integer;
69use crate::reader::Reader;
70
71const MAX_DEPTH: u8 = 2;
74
75const SHARE_DIVISOR: usize = 20;
79
80pub(crate) const SAMPLE_BYTES: usize = 64 * 1024;
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum Kind {
90 Constant = 0,
92 Plain = 1,
94 Fsst = 2,
96 Dict = 3,
98 Front = 4,
100}
101
102impl Kind {
103 fn tag(self) -> u8 {
104 self as u8
105 }
106
107 fn from_tag(tag: u8) -> Result<Self> {
108 match tag {
109 0 => Ok(Self::Constant),
110 1 => Ok(Self::Plain),
111 2 => Ok(Self::Fsst),
112 3 => Ok(Self::Dict),
113 4 => Ok(Self::Front),
114 other => Err(Error::internal(format!("unknown string encoding tag {other}"))),
115 }
116 }
117
118 #[must_use]
120 pub fn name(self) -> &'static str {
121 match self {
122 Self::Constant => "CONSTANT",
123 Self::Plain => "PLAIN",
124 Self::Fsst => "FSST",
125 Self::Dict => "DICT",
126 Self::Front => "FRONT",
127 }
128 }
129}
130
131pub fn encode(values: &[&[u8]]) -> Result<Vec<u8>> {
142 encode_with(values, &EXHAUSTIVE)
143}
144
145pub fn encode_with(values: &[&[u8]], chooser: &dyn Chooser) -> Result<Vec<u8>> {
155 encode_at(values, 0, chooser)
156}
157
158pub fn decode_prefix(bytes: &[u8]) -> Result<(Vec<Vec<u8>>, usize)> {
167 let mut reader = Reader::new(bytes);
168 let values = decode_chunk(&mut reader)?;
169 Ok((values, reader.used()))
170}
171
172pub fn describe_prefix(bytes: &[u8]) -> Result<(String, usize)> {
178 let mut reader = Reader::new(bytes);
179 let text = describe_chunk(&mut reader)?;
180 Ok((text, reader.used()))
181}
182
183pub fn decode(bytes: &[u8]) -> Result<Vec<Vec<u8>>> {
189 let mut reader = Reader::new(bytes);
190 let values = decode_chunk(&mut reader)?;
191 if reader.remaining() != 0 {
192 return Err(Error::internal(format!(
193 "{} bytes left over after decoding a string chunk",
194 reader.remaining()
195 )));
196 }
197 Ok(values)
198}
199
200pub fn candidate_sizes(values: &[&[u8]]) -> Result<Vec<(Kind, usize)>> {
207 let mut sizes = Vec::new();
208 for kind in candidates(values, 0) {
209 if let Some(bytes) = encode_as(kind, values, 0, &EXHAUSTIVE)? {
210 sizes.push((kind, bytes.len()));
211 }
212 }
213 Ok(sizes)
214}
215
216#[must_use]
223pub fn offered(values: &[&[u8]]) -> Vec<Kind> {
224 candidates(values, 0)
225}
226
227pub fn encode_only(kind: Kind, values: &[&[u8]]) -> Result<Option<Vec<u8>>> {
239 encode_as(kind, values, 0, &EXHAUSTIVE)
240}
241
242pub(crate) fn size_as(kind: Kind, values: &[&[u8]], depth: u8) -> Result<Option<usize>> {
247 Ok(encode_as(kind, values, depth, &EXHAUSTIVE)?.map(|bytes| bytes.len()))
248}
249
250pub fn describe(bytes: &[u8]) -> Result<String> {
256 let mut reader = Reader::new(bytes);
257 describe_chunk(&mut reader)
258}
259
260fn encode_at(values: &[&[u8]], depth: u8, chooser: &dyn Chooser) -> Result<Vec<u8>> {
261 let offered = candidates(values, depth);
262 let mut best: Option<Vec<u8>> = None;
263 for kind in chooser.narrow_strings(values, &offered, depth) {
264 let Some(bytes) = encode_as(kind, values, depth, chooser)? else {
265 continue;
266 };
267 if best.as_ref().is_none_or(|current| bytes.len() < current.len()) {
268 best = Some(bytes);
269 }
270 }
271 best.ok_or_else(|| Error::internal("no string encoding applied to the chunk"))
272}
273
274fn candidates(values: &[&[u8]], depth: u8) -> Vec<Kind> {
275 let mut kinds = vec![Kind::Plain];
276 if values.is_empty() {
277 return kinds;
278 }
279 if values.iter().all(|value| *value == values[0]) {
280 return vec![Kind::Constant];
281 }
282 kinds.push(Kind::Fsst);
283 if depth < MAX_DEPTH && has_duplicates(values) {
284 kinds.push(Kind::Dict);
285 }
286 if depth < MAX_DEPTH && sharing_of(values) >= total_len(values) / SHARE_DIVISOR {
287 kinds.push(Kind::Front);
288 }
289 kinds
290}
291
292fn sharing_of(values: &[&[u8]]) -> usize {
299 let mut shared = 0;
300 for pair in values.windows(2) {
301 shared += shared_prefix(pair[0], pair[1]);
302 }
303 shared
304}
305
306pub(crate) fn front_code<'a>(values: &[&'a [u8]]) -> (Vec<i64>, Vec<&'a [u8]>) {
312 let mut prefixes = Vec::with_capacity(values.len());
313 let mut suffixes: Vec<&'a [u8]> = Vec::with_capacity(values.len());
314 let mut previous: &[u8] = b"";
315 for value in values {
316 let value: &'a [u8] = value;
317 let shared = shared_prefix(previous, value);
318 prefixes.push(shared as i64);
319 suffixes.push(&value[shared..]);
320 previous = value;
321 }
322 (prefixes, suffixes)
323}
324
325pub(crate) fn front_decode(prefixes: &[i64], suffixes: Vec<Vec<u8>>) -> Result<Vec<Vec<u8>>> {
332 let mut values: Vec<Vec<u8>> = Vec::with_capacity(suffixes.len());
333 for (index, suffix) in suffixes.into_iter().enumerate() {
334 let shared = usize::try_from(prefixes[index])
335 .map_err(|_| Error::internal("a negative shared prefix length"))?;
336 let previous: &[u8] = if index == 0 { b"" } else { &values[index - 1] };
337 if shared > previous.len() {
338 return Err(Error::internal(format!(
339 "a value shares {shared} bytes with a value {} bytes long",
340 previous.len()
341 )));
342 }
343 let mut value = Vec::with_capacity(shared + suffix.len());
344 value.extend_from_slice(&previous[..shared]);
345 value.extend_from_slice(&suffix);
346 values.push(value);
347 }
348 Ok(values)
349}
350
351fn shared_prefix(previous: &[u8], value: &[u8]) -> usize {
352 let limit = previous.len().min(value.len());
353 let mut shared = 0;
354 while shared < limit && previous[shared] == value[shared] {
355 shared += 1;
356 }
357 shared
358}
359
360fn total_len(values: &[&[u8]]) -> usize {
361 values.iter().map(|value| value.len()).sum()
362}
363
364fn encode_as(
365 kind: Kind,
366 values: &[&[u8]],
367 depth: u8,
368 chooser: &dyn Chooser,
369) -> Result<Option<Vec<u8>>> {
370 let mut out = vec![kind.tag()];
371 put_u32(&mut out, u32::try_from(values.len()).map_err(|_| too_long(values.len()))?);
372 match kind {
373 Kind::Constant => {
374 let Some(first) = values.first() else {
375 return Ok(None);
376 };
377 if values.iter().any(|value| value != first) {
378 return Ok(None);
379 }
380 put_u32(&mut out, u32::try_from(first.len()).map_err(|_| too_long(first.len()))?);
381 out.extend_from_slice(first);
382 }
383 Kind::Plain => {
384 out.extend_from_slice(&encode_lengths(values, chooser)?);
385 for value in values {
386 out.extend_from_slice(value);
387 }
388 }
389 Kind::Fsst => {
390 let sample = sample_of(values);
391 let table = SymbolTable::train(&sample);
392 if table.is_empty() {
393 return Ok(None);
394 }
395 let mut compressed = Vec::new();
396 let mut lengths = Vec::with_capacity(values.len());
397 for value in values {
398 let before = compressed.len();
399 table.compress(value, &mut compressed);
400 lengths.push((compressed.len() - before) as i64);
401 }
402 table.serialize(&mut out);
403 out.extend_from_slice(&integer::encode_with(&lengths, chooser)?);
404 out.extend_from_slice(&compressed);
405 }
406 Kind::Dict => {
407 let (entries, codes) = dictionary_of(values);
408 if entries.is_empty() {
409 return Ok(None);
410 }
411 out.extend_from_slice(&encode_at(&entries, depth + 1, chooser)?);
412 out.extend_from_slice(&integer::encode_with(&codes, chooser)?);
413 }
414 Kind::Front => {
415 let (prefixes, suffixes) = front_code(values);
416 out.extend_from_slice(&integer::encode_with(&prefixes, chooser)?);
417 out.extend_from_slice(&encode_at(&suffixes, depth + 1, chooser)?);
418 }
419 }
420 Ok(Some(out))
421}
422
423fn decode_chunk(reader: &mut Reader<'_>) -> Result<Vec<Vec<u8>>> {
424 let kind = Kind::from_tag(reader.u8()?)?;
425 let count = reader.u32()? as usize;
426 match kind {
427 Kind::Constant => {
428 let len = reader.u32()? as usize;
429 let value = reader.bytes(len)?.to_vec();
430 Ok(vec![value; count])
431 }
432 Kind::Plain => {
433 let lengths = decode_lengths(reader, count)?;
434 let mut values = Vec::with_capacity(count);
435 for length in lengths {
436 values.push(reader.bytes(length)?.to_vec());
437 }
438 Ok(values)
439 }
440 Kind::Fsst => {
441 let (table, used) = SymbolTable::deserialize(reader.rest())?;
442 reader.skip(used)?;
443 let lengths = decode_lengths(reader, count)?;
444 let mut values = Vec::with_capacity(count);
445 for length in lengths {
446 let compressed = reader.bytes(length)?;
447 let mut value = Vec::new();
448 table.decompress(compressed, &mut value)?;
449 values.push(value);
450 }
451 Ok(values)
452 }
453 Kind::Dict => {
454 let dictionary = decode_chunk(reader)?;
455 let codes = decode_integers(reader)?;
456 if codes.len() != count {
457 return Err(Error::internal(format!(
458 "a dictionary chunk says it holds {count} values and has {} codes",
459 codes.len()
460 )));
461 }
462 let mut values = Vec::with_capacity(count);
463 for code in codes {
464 let entry =
465 usize::try_from(code).ok().and_then(|index| dictionary.get(index)).ok_or_else(
466 || Error::internal(format!("code {code} is not in the dictionary")),
467 )?;
468 values.push(entry.clone());
469 }
470 Ok(values)
471 }
472 Kind::Front => {
473 let prefixes = decode_integers(reader)?;
474 let suffixes = decode_chunk(reader)?;
475 if prefixes.len() != count || suffixes.len() != count {
476 return Err(Error::internal(format!(
477 "a front coded chunk says it holds {count} values and has {} prefixes and {} suffixes",
478 prefixes.len(),
479 suffixes.len()
480 )));
481 }
482 front_decode(&prefixes, suffixes)
483 }
484 }
485}
486
487fn describe_chunk(reader: &mut Reader<'_>) -> Result<String> {
488 let kind = Kind::from_tag(reader.u8()?)?;
489 let count = reader.u32()? as usize;
490 Ok(match kind {
491 Kind::Constant => {
492 let len = reader.u32()? as usize;
493 reader.bytes(len)?;
494 "CONSTANT".to_string()
495 }
496 Kind::Plain => {
497 let (shape, lengths) = describe_lengths(reader, count)?;
498 reader.skip(lengths.iter().sum())?;
499 format!("PLAIN({shape})")
500 }
501 Kind::Fsst => {
502 let (table, used) = SymbolTable::deserialize(reader.rest())?;
503 reader.skip(used)?;
504 let (shape, lengths) = describe_lengths(reader, count)?;
505 reader.skip(lengths.iter().sum())?;
506 format!("FSST[{}]({shape})", table.len())
507 }
508 Kind::Dict => {
509 let entries = describe_chunk(reader)?;
510 let codes = describe_integers(reader)?;
511 format!("DICT({entries}, {codes})")
512 }
513 Kind::Front => {
514 let prefixes = describe_integers(reader)?;
515 let suffixes = describe_chunk(reader)?;
516 format!("FRONT({prefixes}, {suffixes})")
517 }
518 })
519}
520
521fn describe_lengths(reader: &mut Reader<'_>, count: usize) -> Result<(String, Vec<usize>)> {
525 let (shape, _) = integer::describe_prefix(reader.rest())?;
526 let lengths = decode_lengths(reader, count)?;
527 Ok((shape, lengths))
528}
529
530fn encode_lengths(values: &[&[u8]], chooser: &dyn Chooser) -> Result<Vec<u8>> {
531 let lengths: Vec<i64> = values.iter().map(|value| value.len() as i64).collect();
532 integer::encode_with(&lengths, chooser)
533}
534
535fn decode_lengths(reader: &mut Reader<'_>, count: usize) -> Result<Vec<usize>> {
536 let lengths = decode_integers(reader)?;
537 if lengths.len() != count {
538 return Err(Error::internal(format!(
539 "a string chunk says it holds {count} values and has {} lengths",
540 lengths.len()
541 )));
542 }
543 lengths
544 .into_iter()
545 .map(|length| {
546 usize::try_from(length).map_err(|_| Error::internal("a negative string length"))
547 })
548 .collect()
549}
550
551fn decode_integers(reader: &mut Reader<'_>) -> Result<Vec<i64>> {
555 let (values, used) = integer::decode_prefix(reader.rest())?;
556 reader.skip(used)?;
557 Ok(values)
558}
559
560fn describe_integers(reader: &mut Reader<'_>) -> Result<String> {
561 let (text, used) = integer::describe_prefix(reader.rest())?;
562 reader.skip(used)?;
563 Ok(text)
564}
565
566pub(crate) fn sample_of<'a>(values: &[&'a [u8]]) -> Vec<&'a [u8]> {
585 sample_bytes_of(values, SAMPLE_BYTES)
586}
587
588pub(crate) fn sample_bytes_of<'a>(values: &[&'a [u8]], budget: usize) -> Vec<&'a [u8]> {
591 let budget = budget.max(1);
592 let total: usize = values.iter().map(|value| value.len()).sum();
593 if total <= budget {
594 return values.to_vec();
595 }
596 let stride = total.div_ceil(budget).max(1);
597 let span = (stride * 2 - 1).max(1) as u64;
598 let mut state = 0x2545_f491_4f6c_dd1du64;
599 let mut sample = Vec::with_capacity(values.len() / stride + 1);
600 let mut at = 0usize;
601 while at < values.len() {
602 sample.push(values[at]);
603 state ^= state << 13;
604 state ^= state >> 7;
605 state ^= state << 17;
606 at += 1 + (state % span) as usize;
607 }
608 sample
609}
610
611fn dictionary_of<'a>(values: &[&'a [u8]]) -> (Vec<&'a [u8]>, Vec<i64>) {
623 let mut order: Vec<u32> = (0..values.len() as u32).collect();
624 order.sort_unstable_by(|left, right| values[*left as usize].cmp(values[*right as usize]));
625 let mut entries: Vec<&'a [u8]> = Vec::new();
626 let mut codes = vec![0i64; values.len()];
627 for &index in &order {
628 let value = values[index as usize];
629 if entries.last() != Some(&value) {
630 entries.push(value);
631 }
632 codes[index as usize] = (entries.len() - 1) as i64;
633 }
634 (entries, codes)
635}
636
637fn has_duplicates(values: &[&[u8]]) -> bool {
647 let Some(slots) = values.len().checked_mul(2).map(usize::next_power_of_two) else {
648 return false;
649 };
650 let mask = slots - 1;
651 let mut table = vec![u32::MAX; slots];
652 for (index, value) in values.iter().enumerate() {
653 let mut at = hash_of(value) as usize & mask;
654 loop {
655 let held = table[at];
656 if held == u32::MAX {
657 table[at] = index as u32;
658 break;
659 }
660 if values[held as usize] == *value {
661 return true;
662 }
663 at = (at + 1) & mask;
664 }
665 }
666 false
667}
668
669fn hash_of(value: &[u8]) -> u64 {
676 let mut hash = 0xcbf2_9ce4_8422_2325_u64;
677 let mut chunks = value.chunks_exact(8);
678 for chunk in &mut chunks {
679 let word = u64::from_le_bytes(chunk.try_into().expect("chunks_exact(8) gives eight bytes"));
680 hash = (hash ^ word).wrapping_mul(0x1_0000_01b3);
681 }
682 for byte in chunks.remainder() {
683 hash = (hash ^ u64::from(*byte)).wrapping_mul(0x1_0000_01b3);
684 }
685 (hash ^ (value.len() as u64)).wrapping_mul(0x1_0000_01b3)
686}
687
688fn too_long(len: usize) -> Error {
689 Error::internal(format!("a string chunk of {len} is longer than the format allows"))
690}
691
692fn put_u32(out: &mut Vec<u8>, value: u32) {
693 out.extend_from_slice(&value.to_le_bytes());
694}
695
696#[cfg(test)]
697mod tests {
698 use super::*;
699
700 fn urls(count: usize) -> Vec<Vec<u8>> {
701 let hosts = ["www.example.com", "shop.example.com", "news.other.example.org"];
702 let paths = ["/index.html", "/catalog/item", "/search", "/user/profile/settings"];
703 (0..count)
704 .map(|index| {
705 let host = hosts[index % hosts.len()];
706 let path = paths[(index / 3) % paths.len()];
707 format!("http://{host}{path}?session={}&ref=google", index * 7).into_bytes()
708 })
709 .collect()
710 }
711
712 fn keyed(values: Vec<Vec<u8>>) -> Vec<Vec<u8>> {
716 values
717 .into_iter()
718 .enumerate()
719 .map(|(index, value)| {
720 let key = (index as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15) % 1_000_000_007;
721 let mut out = format!("{key:010}/").into_bytes();
722 out.extend_from_slice(&value);
723 out
724 })
725 .collect()
726 }
727
728 fn borrow(values: &[Vec<u8>]) -> Vec<&[u8]> {
729 values.iter().map(Vec::as_slice).collect()
730 }
731
732 fn round_trip(values: &[Vec<u8>]) -> Vec<u8> {
733 let borrowed = borrow(values);
734 let bytes = encode(&borrowed).unwrap();
735 let back = decode(&bytes).unwrap();
736 assert_eq!(back, values, "{}", describe(&bytes).unwrap());
737 bytes
738 }
739
740 fn kind_of(bytes: &[u8]) -> Kind {
741 Kind::from_tag(bytes[0]).unwrap()
742 }
743
744 #[test]
745 fn the_dictionary_is_sorted_and_the_codes_point_back_at_the_values() {
746 let values = vec![
749 b"pear".to_vec(),
750 b"apple".to_vec(),
751 b"pear".to_vec(),
752 b"cherry".to_vec(),
753 b"apple".to_vec(),
754 ];
755 let borrowed = borrow(&values);
756 let (entries, codes) = dictionary_of(&borrowed);
757 assert_eq!(entries, vec![b"apple".as_slice(), b"cherry".as_slice(), b"pear".as_slice()]);
758 assert_eq!(codes, vec![2, 0, 2, 1, 0]);
759 for (code, value) in codes.iter().zip(&borrowed) {
760 assert_eq!(entries[*code as usize], *value);
761 }
762 }
763
764 #[test]
765 fn a_column_with_nothing_repeated_has_no_duplicates_and_one_with_anything_does() {
766 let distinct: Vec<Vec<u8>> =
767 (0..5000).map(|index| format!("value-{index}").into_bytes()).collect();
768 assert!(!has_duplicates(&borrow(&distinct)));
769
770 let mut repeated = distinct.clone();
772 repeated.push(b"value-0".to_vec());
773 assert!(has_duplicates(&borrow(&repeated)));
774
775 assert!(!has_duplicates(&borrow(&Vec::new())));
776 assert!(!has_duplicates(&borrow(&[b"one".to_vec()])));
777 assert!(has_duplicates(&borrow(&vec![b"same".to_vec(); 2])));
778 }
779
780 #[test]
781 fn long_values_that_differ_only_at_the_end_are_not_confused_for_each_other() {
782 let stem = "http://www.example.com/a/very/long/path/that/goes/on?session=";
785 let values: Vec<Vec<u8>> =
786 (0..2000).map(|index| format!("{stem}{index}").into_bytes()).collect();
787 assert!(!has_duplicates(&borrow(&values)));
788 let (entries, codes) = dictionary_of(&borrow(&values));
789 assert_eq!(entries.len(), values.len());
790 assert_eq!(codes.len(), values.len());
791 }
792
793 #[test]
794 fn what_the_chooser_returns_is_the_smallest_of_what_it_was_offered() {
795 for values in [urls(400), keyed(urls(400)), vec![b"same".to_vec(); 50], Vec::new()] {
801 let borrowed = borrow(&values);
802 let chosen = encode(&borrowed).unwrap();
803 let mut smallest: Option<Vec<u8>> = None;
804 for kind in offered(&borrowed) {
805 let Some(bytes) = encode_only(kind, &borrowed).unwrap() else {
806 continue;
807 };
808 if smallest.as_ref().is_none_or(|best| bytes.len() < best.len()) {
809 smallest = Some(bytes);
810 }
811 }
812 assert_eq!(smallest.as_deref(), Some(chosen.as_slice()), "{}", values.len());
813 }
814 }
815
816 fn raw_size(values: &[Vec<u8>]) -> usize {
817 values.iter().map(Vec::len).sum::<usize>() + values.len() * 4
818 }
819
820 #[test]
821 fn an_empty_chunk_round_trips() {
822 let bytes = round_trip(&[]);
823 assert_eq!(kind_of(&bytes), Kind::Plain);
824 }
825
826 #[test]
827 fn a_constant_column_costs_what_one_value_costs() {
828 let values = vec![b"https://www.example.com/".to_vec(); 100_000];
829 let bytes = round_trip(&values);
830 assert_eq!(kind_of(&bytes), Kind::Constant);
831 assert_eq!(bytes.len(), 9 + 24);
832 }
833
834 #[test]
835 fn a_url_column_of_unique_values_uses_fsst() {
836 let values = keyed(urls(20_000));
842 let bytes = round_trip(&values);
843 assert_eq!(kind_of(&bytes), Kind::Fsst);
844 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
847 assert!(ratio > 4.0, "{ratio:.2}x");
848 }
849
850 #[test]
851 fn a_sample_of_a_periodic_column_learns_every_phase_of_it() {
852 let values = urls(20_000);
857 let borrowed = borrow(&values);
858 let sample = sample_of(&borrowed);
859 let mut phases: Vec<&[u8]> = sample
860 .iter()
861 .map(|value| {
862 let query =
863 value.iter().position(|byte| *byte == b'?').expect("every value has a query");
864 &value[..query]
865 })
866 .collect();
867 phases.sort_unstable();
868 phases.dedup();
869 assert_eq!(phases.len(), 12);
871 let whole = SymbolTable::train(&borrowed);
872 let sampled = SymbolTable::train(&sample);
873 let mut on_whole = Vec::new();
874 let mut on_sample = Vec::new();
875 for value in &borrowed {
876 whole.compress(value, &mut on_whole);
877 sampled.compress(value, &mut on_sample);
878 }
879 assert!(
882 on_sample.len() < on_whole.len() * 5 / 4,
883 "{} against {}",
884 on_sample.len(),
885 on_whole.len()
886 );
887 }
888
889 #[test]
890 fn a_repeating_column_becomes_a_dictionary_of_compressed_entries() {
891 let distinct = urls(500);
896 let values: Vec<Vec<u8>> =
897 (0..50_000).map(|index| distinct[index * 7919 % distinct.len()].clone()).collect();
898 let bytes = round_trip(&values);
899 assert_eq!(kind_of(&bytes), Kind::Dict);
900 let shape = describe(&bytes).unwrap();
901 assert!(shape.starts_with("DICT(FRONT("), "{shape}");
902 assert!(shape.contains("FSST"), "{shape}");
903 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
904 assert!(ratio > 20.0, "{ratio:.2}x, {shape}");
905 }
906
907 #[test]
908 fn a_column_of_long_runs_costs_almost_nothing() {
909 let distinct = urls(50);
912 let mut values = Vec::new();
913 for entry in &distinct {
914 values.extend(std::iter::repeat_n(entry.clone(), 1000));
915 }
916 let bytes = round_trip(&values);
917 let shape = describe(&bytes).unwrap();
918 assert!(shape.contains("RLE"), "{shape}");
919 assert!(bytes.len() < 2000, "{} bytes: {shape}", bytes.len());
920 }
921
922 #[test]
923 fn incompressible_strings_stay_close_to_their_own_size() {
924 let mut state = 0x2545_f491_4f6c_dd1du64;
927 let values: Vec<Vec<u8>> = (0..2000)
928 .map(|_| {
929 (0..32)
930 .map(|_| {
931 state ^= state << 13;
932 state ^= state >> 7;
933 state ^= state << 17;
934 state as u8
935 })
936 .collect()
937 })
938 .collect();
939 let bytes = round_trip(&values);
940 assert!(bytes.len() < 2000 * 32 + 3000, "{} bytes", bytes.len());
941 }
942
943 #[test]
944 fn lengths_are_stored_rather_than_offsets() {
945 let values: Vec<Vec<u8>> =
948 (0..100_000).map(|index| format!("{index:024}").into_bytes()).collect();
949 let borrowed = borrow(&values);
950 let bytes = encode_only(Kind::Plain, &borrowed).unwrap().unwrap();
951 assert_eq!(bytes.len(), 5 + 13 + 100_000 * 24);
952 }
953
954 #[test]
955 fn empty_strings_are_values_and_not_nulls() {
956 let values = vec![Vec::new(), b"a".to_vec(), Vec::new(), b"bb".to_vec()];
957 round_trip(&values);
958 }
959
960 #[test]
961 fn a_chunk_with_one_value_round_trips() {
962 round_trip(&[b"only".to_vec()]);
963 }
964
965 #[test]
966 fn every_candidate_that_applies_decodes_to_the_input() {
967 let values = urls(3000);
968 let borrowed = borrow(&values);
969 let applicable = candidates(&borrowed, 0);
970 assert!(applicable.len() >= 2, "{applicable:?}");
971 for kind in applicable {
972 let bytes = encode_only(kind, &borrowed).unwrap().unwrap();
973 assert_eq!(decode(&bytes).unwrap(), values, "{}", kind.name());
974 }
975 }
976
977 #[test]
978 fn the_chooser_picks_the_smallest_candidate() {
979 let values = urls(2000);
980 let borrowed = borrow(&values);
981 let chosen = encode(&borrowed).unwrap();
982 for (_, size) in candidate_sizes(&borrowed).unwrap() {
983 assert!(chosen.len() <= size);
984 }
985 }
986
987 #[test]
988 fn a_truncated_chunk_is_an_error_and_not_a_panic() {
989 let values = urls(40);
990 let bytes = encode(&borrow(&values)).unwrap();
991 for len in 0..bytes.len() {
992 assert!(decode(&bytes[..len]).is_err(), "{len} bytes decoded");
993 }
994 }
995
996 #[test]
997 fn trailing_bytes_are_an_error() {
998 let mut bytes = encode(&borrow(&urls(10))).unwrap();
999 bytes.push(0);
1000 let error = decode(&bytes).unwrap_err();
1001 assert!(error.message().contains("left over"), "{error}");
1002 }
1003
1004 #[test]
1005 fn an_unknown_tag_is_an_error() {
1006 let error = decode(&[99, 0, 0, 0, 0]).unwrap_err();
1007 assert!(error.message().contains("unknown string encoding tag"), "{error}");
1008 }
1009
1010 #[test]
1011 fn a_dictionary_code_outside_the_dictionary_is_an_error() {
1012 let mut bytes = vec![Kind::Dict.tag()];
1013 put_u32(&mut bytes, 1);
1014 bytes.extend_from_slice(&encode(&[b"one".as_slice()]).unwrap());
1015 bytes.extend_from_slice(&integer::encode(&[9]).unwrap());
1016 let error = decode(&bytes).unwrap_err();
1017 assert!(error.message().contains("not in the dictionary"), "{error}");
1018 }
1019
1020 #[test]
1021 fn a_sorted_column_of_urls_is_front_coded() {
1022 let mut values = urls(20_000);
1026 values.sort();
1027 let bytes = round_trip(&values);
1028 assert_eq!(kind_of(&bytes), Kind::Front);
1029 let shape = describe(&bytes).unwrap();
1030 let mut plain = Vec::new();
1031 let borrowed = borrow(&values);
1032 for (kind, size) in candidate_sizes(&borrowed).unwrap() {
1033 if kind == Kind::Fsst {
1034 plain.push(size);
1035 }
1036 }
1037 let fsst = plain[0];
1038 assert!(bytes.len() * 2 < fsst, "{} against FSST {fsst}: {shape}", bytes.len());
1039 }
1040
1041 #[test]
1042 fn a_column_with_nothing_to_share_is_not_offered_front_coding() {
1043 let mut state = 0x9e37_79b9_7f4a_7c15u64;
1046 let values: Vec<Vec<u8>> = (0..2000)
1047 .map(|_| {
1048 (0..24)
1049 .map(|_| {
1050 state ^= state << 13;
1051 state ^= state >> 7;
1052 state ^= state << 17;
1053 (state % 251) as u8
1054 })
1055 .collect()
1056 })
1057 .collect();
1058 let borrowed = borrow(&values);
1059 assert!(!candidates(&borrowed, 0).contains(&Kind::Front));
1060 }
1061
1062 #[test]
1063 fn a_prefix_longer_than_the_value_before_it_is_an_error() {
1064 let mut bytes = vec![Kind::Front.tag()];
1065 put_u32(&mut bytes, 2);
1066 bytes.extend_from_slice(&integer::encode(&[0, 9]).unwrap());
1067 bytes.extend_from_slice(&encode(&[b"one".as_slice(), b"two".as_slice()]).unwrap());
1068 let error = decode(&bytes).unwrap_err();
1069 assert!(error.message().contains("shares 9 bytes"), "{error}");
1070 }
1071
1072 #[test]
1073 fn a_negative_prefix_is_an_error() {
1074 let mut bytes = vec![Kind::Front.tag()];
1075 put_u32(&mut bytes, 1);
1076 bytes.extend_from_slice(&integer::encode(&[-1]).unwrap());
1077 bytes.extend_from_slice(&encode(&[b"one".as_slice()]).unwrap());
1078 let error = decode(&bytes).unwrap_err();
1079 assert!(error.message().contains("negative shared prefix"), "{error}");
1080 }
1081
1082 #[test]
1083 fn a_negative_length_is_an_error() {
1084 let mut bytes = vec![Kind::Plain.tag()];
1085 put_u32(&mut bytes, 1);
1086 bytes.extend_from_slice(&integer::encode(&[-1]).unwrap());
1087 let error = decode(&bytes).unwrap_err();
1088 assert!(error.message().contains("negative string length"), "{error}");
1089 }
1090
1091 #[test]
1092 fn the_sample_is_spread_across_the_chunk_and_not_taken_from_the_front() {
1093 let mut values: Vec<Vec<u8>> = Vec::new();
1096 for index in 0..20_000 {
1097 let head = if index < 10_000 { "aaaaaaaaaaaaaaaa" } else { "zzzzzzzzzzzzzzzz" };
1098 values.push(format!("{head}/{index:08}").into_bytes());
1099 }
1100 let borrowed = borrow(&values);
1101 let sample = sample_of(&borrowed);
1102 let first_half = sample.iter().filter(|value| value.starts_with(b"aaaa")).count();
1103 let second_half = sample.len() - first_half;
1104 assert!(first_half > 0 && second_half > 0, "{first_half} and {second_half}");
1105 let bytes = round_trip(&values);
1106 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1107 assert!(ratio > 4.0, "{ratio:.2}x");
1108 }
1109}