1use rudb_common::{Error, Result};
65
66use crate::fsst::SymbolTable;
67use crate::integer;
68use crate::reader::Reader;
69
70const MAX_DEPTH: u8 = 2;
73
74const SHARE_DIVISOR: usize = 20;
78
79pub(crate) const SAMPLE_BYTES: usize = 64 * 1024;
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum Kind {
89 Constant = 0,
91 Plain = 1,
93 Fsst = 2,
95 Dict = 3,
97 Front = 4,
99}
100
101impl Kind {
102 fn tag(self) -> u8 {
103 self as u8
104 }
105
106 fn from_tag(tag: u8) -> Result<Self> {
107 match tag {
108 0 => Ok(Self::Constant),
109 1 => Ok(Self::Plain),
110 2 => Ok(Self::Fsst),
111 3 => Ok(Self::Dict),
112 4 => Ok(Self::Front),
113 other => Err(Error::internal(format!("unknown string encoding tag {other}"))),
114 }
115 }
116
117 #[must_use]
119 pub fn name(self) -> &'static str {
120 match self {
121 Self::Constant => "CONSTANT",
122 Self::Plain => "PLAIN",
123 Self::Fsst => "FSST",
124 Self::Dict => "DICT",
125 Self::Front => "FRONT",
126 }
127 }
128}
129
130pub fn encode(values: &[&[u8]]) -> Result<Vec<u8>> {
137 encode_at(values, 0)
138}
139
140pub fn decode_prefix(bytes: &[u8]) -> Result<(Vec<Vec<u8>>, usize)> {
149 let mut reader = Reader::new(bytes);
150 let values = decode_chunk(&mut reader)?;
151 Ok((values, reader.used()))
152}
153
154pub fn describe_prefix(bytes: &[u8]) -> Result<(String, usize)> {
160 let mut reader = Reader::new(bytes);
161 let text = describe_chunk(&mut reader)?;
162 Ok((text, reader.used()))
163}
164
165pub fn decode(bytes: &[u8]) -> Result<Vec<Vec<u8>>> {
171 let mut reader = Reader::new(bytes);
172 let values = decode_chunk(&mut reader)?;
173 if reader.remaining() != 0 {
174 return Err(Error::internal(format!(
175 "{} bytes left over after decoding a string chunk",
176 reader.remaining()
177 )));
178 }
179 Ok(values)
180}
181
182pub fn candidate_sizes(values: &[&[u8]]) -> Result<Vec<(Kind, usize)>> {
189 let mut sizes = Vec::new();
190 for kind in candidates(values, 0) {
191 if let Some(bytes) = encode_as(kind, values, 0)? {
192 sizes.push((kind, bytes.len()));
193 }
194 }
195 Ok(sizes)
196}
197
198pub fn describe(bytes: &[u8]) -> Result<String> {
204 let mut reader = Reader::new(bytes);
205 describe_chunk(&mut reader)
206}
207
208fn encode_at(values: &[&[u8]], depth: u8) -> Result<Vec<u8>> {
209 let mut best: Option<Vec<u8>> = None;
210 for kind in candidates(values, depth) {
211 let Some(bytes) = encode_as(kind, values, depth)? else {
212 continue;
213 };
214 if best.as_ref().is_none_or(|current| bytes.len() < current.len()) {
215 best = Some(bytes);
216 }
217 }
218 best.ok_or_else(|| Error::internal("no string encoding applied to the chunk"))
219}
220
221fn candidates(values: &[&[u8]], depth: u8) -> Vec<Kind> {
222 let mut kinds = vec![Kind::Plain];
223 if values.is_empty() {
224 return kinds;
225 }
226 if values.iter().all(|value| *value == values[0]) {
227 return vec![Kind::Constant];
228 }
229 kinds.push(Kind::Fsst);
230 if depth < MAX_DEPTH && distinct_values(values).len() < values.len() {
231 kinds.push(Kind::Dict);
232 }
233 if depth < MAX_DEPTH && sharing_of(values) >= total_len(values) / SHARE_DIVISOR {
234 kinds.push(Kind::Front);
235 }
236 kinds
237}
238
239fn sharing_of(values: &[&[u8]]) -> usize {
246 let mut shared = 0;
247 for pair in values.windows(2) {
248 shared += shared_prefix(pair[0], pair[1]);
249 }
250 shared
251}
252
253pub(crate) fn front_code<'a>(values: &[&'a [u8]]) -> (Vec<i64>, Vec<&'a [u8]>) {
259 let mut prefixes = Vec::with_capacity(values.len());
260 let mut suffixes: Vec<&'a [u8]> = Vec::with_capacity(values.len());
261 let mut previous: &[u8] = b"";
262 for value in values {
263 let value: &'a [u8] = value;
264 let shared = shared_prefix(previous, value);
265 prefixes.push(shared as i64);
266 suffixes.push(&value[shared..]);
267 previous = value;
268 }
269 (prefixes, suffixes)
270}
271
272pub(crate) fn front_decode(prefixes: &[i64], suffixes: Vec<Vec<u8>>) -> Result<Vec<Vec<u8>>> {
279 let mut values: Vec<Vec<u8>> = Vec::with_capacity(suffixes.len());
280 for (index, suffix) in suffixes.into_iter().enumerate() {
281 let shared = usize::try_from(prefixes[index])
282 .map_err(|_| Error::internal("a negative shared prefix length"))?;
283 let previous: &[u8] = if index == 0 { b"" } else { &values[index - 1] };
284 if shared > previous.len() {
285 return Err(Error::internal(format!(
286 "a value shares {shared} bytes with a value {} bytes long",
287 previous.len()
288 )));
289 }
290 let mut value = Vec::with_capacity(shared + suffix.len());
291 value.extend_from_slice(&previous[..shared]);
292 value.extend_from_slice(&suffix);
293 values.push(value);
294 }
295 Ok(values)
296}
297
298fn shared_prefix(previous: &[u8], value: &[u8]) -> usize {
299 let limit = previous.len().min(value.len());
300 let mut shared = 0;
301 while shared < limit && previous[shared] == value[shared] {
302 shared += 1;
303 }
304 shared
305}
306
307fn total_len(values: &[&[u8]]) -> usize {
308 values.iter().map(|value| value.len()).sum()
309}
310
311fn encode_as(kind: Kind, values: &[&[u8]], depth: u8) -> Result<Option<Vec<u8>>> {
312 let mut out = vec![kind.tag()];
313 put_u32(&mut out, u32::try_from(values.len()).map_err(|_| too_long(values.len()))?);
314 match kind {
315 Kind::Constant => {
316 let Some(first) = values.first() else {
317 return Ok(None);
318 };
319 if values.iter().any(|value| value != first) {
320 return Ok(None);
321 }
322 put_u32(&mut out, u32::try_from(first.len()).map_err(|_| too_long(first.len()))?);
323 out.extend_from_slice(first);
324 }
325 Kind::Plain => {
326 out.extend_from_slice(&encode_lengths(values)?);
327 for value in values {
328 out.extend_from_slice(value);
329 }
330 }
331 Kind::Fsst => {
332 let sample = sample_of(values);
333 let table = SymbolTable::train(&sample);
334 if table.is_empty() {
335 return Ok(None);
336 }
337 let mut compressed = Vec::new();
338 let mut lengths = Vec::with_capacity(values.len());
339 for value in values {
340 let before = compressed.len();
341 table.compress(value, &mut compressed);
342 lengths.push((compressed.len() - before) as i64);
343 }
344 table.serialize(&mut out);
345 out.extend_from_slice(&integer::encode(&lengths)?);
346 out.extend_from_slice(&compressed);
347 }
348 Kind::Dict => {
349 let dictionary = distinct_values(values);
350 if dictionary.is_empty() {
351 return Ok(None);
352 }
353 let codes = codes_over(values, &dictionary);
354 let entries: Vec<&[u8]> = dictionary.iter().map(Vec::as_slice).collect();
355 out.extend_from_slice(&encode_at(&entries, depth + 1)?);
356 out.extend_from_slice(&integer::encode(&codes)?);
357 }
358 Kind::Front => {
359 let (prefixes, suffixes) = front_code(values);
360 out.extend_from_slice(&integer::encode(&prefixes)?);
361 out.extend_from_slice(&encode_at(&suffixes, depth + 1)?);
362 }
363 }
364 Ok(Some(out))
365}
366
367fn decode_chunk(reader: &mut Reader<'_>) -> Result<Vec<Vec<u8>>> {
368 let kind = Kind::from_tag(reader.u8()?)?;
369 let count = reader.u32()? as usize;
370 match kind {
371 Kind::Constant => {
372 let len = reader.u32()? as usize;
373 let value = reader.bytes(len)?.to_vec();
374 Ok(vec![value; count])
375 }
376 Kind::Plain => {
377 let lengths = decode_lengths(reader, count)?;
378 let mut values = Vec::with_capacity(count);
379 for length in lengths {
380 values.push(reader.bytes(length)?.to_vec());
381 }
382 Ok(values)
383 }
384 Kind::Fsst => {
385 let (table, used) = SymbolTable::deserialize(reader.rest())?;
386 reader.skip(used)?;
387 let lengths = decode_lengths(reader, count)?;
388 let mut values = Vec::with_capacity(count);
389 for length in lengths {
390 let compressed = reader.bytes(length)?;
391 let mut value = Vec::new();
392 table.decompress(compressed, &mut value)?;
393 values.push(value);
394 }
395 Ok(values)
396 }
397 Kind::Dict => {
398 let dictionary = decode_chunk(reader)?;
399 let codes = decode_integers(reader)?;
400 if codes.len() != count {
401 return Err(Error::internal(format!(
402 "a dictionary chunk says it holds {count} values and has {} codes",
403 codes.len()
404 )));
405 }
406 let mut values = Vec::with_capacity(count);
407 for code in codes {
408 let entry =
409 usize::try_from(code).ok().and_then(|index| dictionary.get(index)).ok_or_else(
410 || Error::internal(format!("code {code} is not in the dictionary")),
411 )?;
412 values.push(entry.clone());
413 }
414 Ok(values)
415 }
416 Kind::Front => {
417 let prefixes = decode_integers(reader)?;
418 let suffixes = decode_chunk(reader)?;
419 if prefixes.len() != count || suffixes.len() != count {
420 return Err(Error::internal(format!(
421 "a front coded chunk says it holds {count} values and has {} prefixes and {} suffixes",
422 prefixes.len(),
423 suffixes.len()
424 )));
425 }
426 front_decode(&prefixes, suffixes)
427 }
428 }
429}
430
431fn describe_chunk(reader: &mut Reader<'_>) -> Result<String> {
432 let kind = Kind::from_tag(reader.u8()?)?;
433 let count = reader.u32()? as usize;
434 Ok(match kind {
435 Kind::Constant => {
436 let len = reader.u32()? as usize;
437 reader.bytes(len)?;
438 "CONSTANT".to_string()
439 }
440 Kind::Plain => {
441 let (shape, lengths) = describe_lengths(reader, count)?;
442 reader.skip(lengths.iter().sum())?;
443 format!("PLAIN({shape})")
444 }
445 Kind::Fsst => {
446 let (table, used) = SymbolTable::deserialize(reader.rest())?;
447 reader.skip(used)?;
448 let (shape, lengths) = describe_lengths(reader, count)?;
449 reader.skip(lengths.iter().sum())?;
450 format!("FSST[{}]({shape})", table.len())
451 }
452 Kind::Dict => {
453 let entries = describe_chunk(reader)?;
454 let codes = describe_integers(reader)?;
455 format!("DICT({entries}, {codes})")
456 }
457 Kind::Front => {
458 let prefixes = describe_integers(reader)?;
459 let suffixes = describe_chunk(reader)?;
460 format!("FRONT({prefixes}, {suffixes})")
461 }
462 })
463}
464
465fn describe_lengths(reader: &mut Reader<'_>, count: usize) -> Result<(String, Vec<usize>)> {
469 let (shape, _) = integer::describe_prefix(reader.rest())?;
470 let lengths = decode_lengths(reader, count)?;
471 Ok((shape, lengths))
472}
473
474fn encode_lengths(values: &[&[u8]]) -> Result<Vec<u8>> {
475 let lengths: Vec<i64> = values.iter().map(|value| value.len() as i64).collect();
476 integer::encode(&lengths)
477}
478
479fn decode_lengths(reader: &mut Reader<'_>, count: usize) -> Result<Vec<usize>> {
480 let lengths = decode_integers(reader)?;
481 if lengths.len() != count {
482 return Err(Error::internal(format!(
483 "a string chunk says it holds {count} values and has {} lengths",
484 lengths.len()
485 )));
486 }
487 lengths
488 .into_iter()
489 .map(|length| {
490 usize::try_from(length).map_err(|_| Error::internal("a negative string length"))
491 })
492 .collect()
493}
494
495fn decode_integers(reader: &mut Reader<'_>) -> Result<Vec<i64>> {
499 let (values, used) = integer::decode_prefix(reader.rest())?;
500 reader.skip(used)?;
501 Ok(values)
502}
503
504fn describe_integers(reader: &mut Reader<'_>) -> Result<String> {
505 let (text, used) = integer::describe_prefix(reader.rest())?;
506 reader.skip(used)?;
507 Ok(text)
508}
509
510pub(crate) fn sample_of<'a>(values: &[&'a [u8]]) -> Vec<&'a [u8]> {
529 sample_bytes_of(values, SAMPLE_BYTES)
530}
531
532pub(crate) fn sample_bytes_of<'a>(values: &[&'a [u8]], budget: usize) -> Vec<&'a [u8]> {
535 let budget = budget.max(1);
536 let total: usize = values.iter().map(|value| value.len()).sum();
537 if total <= budget {
538 return values.to_vec();
539 }
540 let stride = total.div_ceil(budget).max(1);
541 let span = (stride * 2 - 1).max(1) as u64;
542 let mut state = 0x2545_f491_4f6c_dd1du64;
543 let mut sample = Vec::with_capacity(values.len() / stride + 1);
544 let mut at = 0usize;
545 while at < values.len() {
546 sample.push(values[at]);
547 state ^= state << 13;
548 state ^= state >> 7;
549 state ^= state << 17;
550 at += 1 + (state % span) as usize;
551 }
552 sample
553}
554
555fn distinct_values(values: &[&[u8]]) -> Vec<Vec<u8>> {
558 let mut distinct: Vec<Vec<u8>> = values.iter().map(|value| value.to_vec()).collect();
559 distinct.sort_unstable();
560 distinct.dedup();
561 distinct
562}
563
564fn codes_over(values: &[&[u8]], dictionary: &[Vec<u8>]) -> Vec<i64> {
565 values
566 .iter()
567 .map(|value| {
568 dictionary
569 .binary_search_by(|entry| entry.as_slice().cmp(value))
570 .expect("the dictionary is the distinct values of this chunk") as i64
571 })
572 .collect()
573}
574
575fn too_long(len: usize) -> Error {
576 Error::internal(format!("a string chunk of {len} is longer than the format allows"))
577}
578
579fn put_u32(out: &mut Vec<u8>, value: u32) {
580 out.extend_from_slice(&value.to_le_bytes());
581}
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586
587 fn urls(count: usize) -> Vec<Vec<u8>> {
588 let hosts = ["www.example.com", "shop.example.com", "news.other.example.org"];
589 let paths = ["/index.html", "/catalog/item", "/search", "/user/profile/settings"];
590 (0..count)
591 .map(|index| {
592 let host = hosts[index % hosts.len()];
593 let path = paths[(index / 3) % paths.len()];
594 format!("http://{host}{path}?session={}&ref=google", index * 7).into_bytes()
595 })
596 .collect()
597 }
598
599 fn keyed(values: Vec<Vec<u8>>) -> Vec<Vec<u8>> {
603 values
604 .into_iter()
605 .enumerate()
606 .map(|(index, value)| {
607 let key = (index as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15) % 1_000_000_007;
608 let mut out = format!("{key:010}/").into_bytes();
609 out.extend_from_slice(&value);
610 out
611 })
612 .collect()
613 }
614
615 fn borrow(values: &[Vec<u8>]) -> Vec<&[u8]> {
616 values.iter().map(Vec::as_slice).collect()
617 }
618
619 fn round_trip(values: &[Vec<u8>]) -> Vec<u8> {
620 let borrowed = borrow(values);
621 let bytes = encode(&borrowed).unwrap();
622 let back = decode(&bytes).unwrap();
623 assert_eq!(back, values, "{}", describe(&bytes).unwrap());
624 bytes
625 }
626
627 fn kind_of(bytes: &[u8]) -> Kind {
628 Kind::from_tag(bytes[0]).unwrap()
629 }
630
631 fn raw_size(values: &[Vec<u8>]) -> usize {
632 values.iter().map(Vec::len).sum::<usize>() + values.len() * 4
633 }
634
635 #[test]
636 fn an_empty_chunk_round_trips() {
637 let bytes = round_trip(&[]);
638 assert_eq!(kind_of(&bytes), Kind::Plain);
639 }
640
641 #[test]
642 fn a_constant_column_costs_what_one_value_costs() {
643 let values = vec![b"https://www.example.com/".to_vec(); 100_000];
644 let bytes = round_trip(&values);
645 assert_eq!(kind_of(&bytes), Kind::Constant);
646 assert_eq!(bytes.len(), 9 + 24);
647 }
648
649 #[test]
650 fn a_url_column_of_unique_values_uses_fsst() {
651 let values = keyed(urls(20_000));
657 let bytes = round_trip(&values);
658 assert_eq!(kind_of(&bytes), Kind::Fsst);
659 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
662 assert!(ratio > 4.0, "{ratio:.2}x");
663 }
664
665 #[test]
666 fn a_sample_of_a_periodic_column_learns_every_phase_of_it() {
667 let values = urls(20_000);
672 let borrowed = borrow(&values);
673 let sample = sample_of(&borrowed);
674 let mut phases: Vec<&[u8]> = sample
675 .iter()
676 .map(|value| {
677 let query =
678 value.iter().position(|byte| *byte == b'?').expect("every value has a query");
679 &value[..query]
680 })
681 .collect();
682 phases.sort_unstable();
683 phases.dedup();
684 assert_eq!(phases.len(), 12);
686 let whole = SymbolTable::train(&borrowed);
687 let sampled = SymbolTable::train(&sample);
688 let mut on_whole = Vec::new();
689 let mut on_sample = Vec::new();
690 for value in &borrowed {
691 whole.compress(value, &mut on_whole);
692 sampled.compress(value, &mut on_sample);
693 }
694 assert!(
697 on_sample.len() < on_whole.len() * 5 / 4,
698 "{} against {}",
699 on_sample.len(),
700 on_whole.len()
701 );
702 }
703
704 #[test]
705 fn a_repeating_column_becomes_a_dictionary_of_compressed_entries() {
706 let distinct = urls(500);
711 let values: Vec<Vec<u8>> =
712 (0..50_000).map(|index| distinct[index * 7919 % distinct.len()].clone()).collect();
713 let bytes = round_trip(&values);
714 assert_eq!(kind_of(&bytes), Kind::Dict);
715 let shape = describe(&bytes).unwrap();
716 assert!(shape.starts_with("DICT(FRONT("), "{shape}");
717 assert!(shape.contains("FSST"), "{shape}");
718 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
719 assert!(ratio > 20.0, "{ratio:.2}x, {shape}");
720 }
721
722 #[test]
723 fn a_column_of_long_runs_costs_almost_nothing() {
724 let distinct = urls(50);
727 let mut values = Vec::new();
728 for entry in &distinct {
729 values.extend(std::iter::repeat_n(entry.clone(), 1000));
730 }
731 let bytes = round_trip(&values);
732 let shape = describe(&bytes).unwrap();
733 assert!(shape.contains("RLE"), "{shape}");
734 assert!(bytes.len() < 2000, "{} bytes: {shape}", bytes.len());
735 }
736
737 #[test]
738 fn incompressible_strings_stay_close_to_their_own_size() {
739 let mut state = 0x2545_f491_4f6c_dd1du64;
742 let values: Vec<Vec<u8>> = (0..2000)
743 .map(|_| {
744 (0..32)
745 .map(|_| {
746 state ^= state << 13;
747 state ^= state >> 7;
748 state ^= state << 17;
749 state as u8
750 })
751 .collect()
752 })
753 .collect();
754 let bytes = round_trip(&values);
755 assert!(bytes.len() < 2000 * 32 + 3000, "{} bytes", bytes.len());
756 }
757
758 #[test]
759 fn lengths_are_stored_rather_than_offsets() {
760 let values: Vec<Vec<u8>> =
763 (0..100_000).map(|index| format!("{index:024}").into_bytes()).collect();
764 let borrowed = borrow(&values);
765 let bytes = encode_as(Kind::Plain, &borrowed, 0).unwrap().unwrap();
766 assert_eq!(bytes.len(), 5 + 13 + 100_000 * 24);
767 }
768
769 #[test]
770 fn empty_strings_are_values_and_not_nulls() {
771 let values = vec![Vec::new(), b"a".to_vec(), Vec::new(), b"bb".to_vec()];
772 round_trip(&values);
773 }
774
775 #[test]
776 fn a_chunk_with_one_value_round_trips() {
777 round_trip(&[b"only".to_vec()]);
778 }
779
780 #[test]
781 fn every_candidate_that_applies_decodes_to_the_input() {
782 let values = urls(3000);
783 let borrowed = borrow(&values);
784 let applicable = candidates(&borrowed, 0);
785 assert!(applicable.len() >= 2, "{applicable:?}");
786 for kind in applicable {
787 let bytes = encode_as(kind, &borrowed, 0).unwrap().unwrap();
788 assert_eq!(decode(&bytes).unwrap(), values, "{}", kind.name());
789 }
790 }
791
792 #[test]
793 fn the_chooser_picks_the_smallest_candidate() {
794 let values = urls(2000);
795 let borrowed = borrow(&values);
796 let chosen = encode(&borrowed).unwrap();
797 for (_, size) in candidate_sizes(&borrowed).unwrap() {
798 assert!(chosen.len() <= size);
799 }
800 }
801
802 #[test]
803 fn a_truncated_chunk_is_an_error_and_not_a_panic() {
804 let values = urls(40);
805 let bytes = encode(&borrow(&values)).unwrap();
806 for len in 0..bytes.len() {
807 assert!(decode(&bytes[..len]).is_err(), "{len} bytes decoded");
808 }
809 }
810
811 #[test]
812 fn trailing_bytes_are_an_error() {
813 let mut bytes = encode(&borrow(&urls(10))).unwrap();
814 bytes.push(0);
815 let error = decode(&bytes).unwrap_err();
816 assert!(error.message().contains("left over"), "{error}");
817 }
818
819 #[test]
820 fn an_unknown_tag_is_an_error() {
821 let error = decode(&[99, 0, 0, 0, 0]).unwrap_err();
822 assert!(error.message().contains("unknown string encoding tag"), "{error}");
823 }
824
825 #[test]
826 fn a_dictionary_code_outside_the_dictionary_is_an_error() {
827 let mut bytes = vec![Kind::Dict.tag()];
828 put_u32(&mut bytes, 1);
829 bytes.extend_from_slice(&encode(&[b"one".as_slice()]).unwrap());
830 bytes.extend_from_slice(&integer::encode(&[9]).unwrap());
831 let error = decode(&bytes).unwrap_err();
832 assert!(error.message().contains("not in the dictionary"), "{error}");
833 }
834
835 #[test]
836 fn a_sorted_column_of_urls_is_front_coded() {
837 let mut values = urls(20_000);
841 values.sort();
842 let bytes = round_trip(&values);
843 assert_eq!(kind_of(&bytes), Kind::Front);
844 let shape = describe(&bytes).unwrap();
845 let mut plain = Vec::new();
846 let borrowed = borrow(&values);
847 for (kind, size) in candidate_sizes(&borrowed).unwrap() {
848 if kind == Kind::Fsst {
849 plain.push(size);
850 }
851 }
852 let fsst = plain[0];
853 assert!(bytes.len() * 2 < fsst, "{} against FSST {fsst}: {shape}", bytes.len());
854 }
855
856 #[test]
857 fn a_column_with_nothing_to_share_is_not_offered_front_coding() {
858 let mut state = 0x9e37_79b9_7f4a_7c15u64;
861 let values: Vec<Vec<u8>> = (0..2000)
862 .map(|_| {
863 (0..24)
864 .map(|_| {
865 state ^= state << 13;
866 state ^= state >> 7;
867 state ^= state << 17;
868 (state % 251) as u8
869 })
870 .collect()
871 })
872 .collect();
873 let borrowed = borrow(&values);
874 assert!(!candidates(&borrowed, 0).contains(&Kind::Front));
875 }
876
877 #[test]
878 fn a_prefix_longer_than_the_value_before_it_is_an_error() {
879 let mut bytes = vec![Kind::Front.tag()];
880 put_u32(&mut bytes, 2);
881 bytes.extend_from_slice(&integer::encode(&[0, 9]).unwrap());
882 bytes.extend_from_slice(&encode(&[b"one".as_slice(), b"two".as_slice()]).unwrap());
883 let error = decode(&bytes).unwrap_err();
884 assert!(error.message().contains("shares 9 bytes"), "{error}");
885 }
886
887 #[test]
888 fn a_negative_prefix_is_an_error() {
889 let mut bytes = vec![Kind::Front.tag()];
890 put_u32(&mut bytes, 1);
891 bytes.extend_from_slice(&integer::encode(&[-1]).unwrap());
892 bytes.extend_from_slice(&encode(&[b"one".as_slice()]).unwrap());
893 let error = decode(&bytes).unwrap_err();
894 assert!(error.message().contains("negative shared prefix"), "{error}");
895 }
896
897 #[test]
898 fn a_negative_length_is_an_error() {
899 let mut bytes = vec![Kind::Plain.tag()];
900 put_u32(&mut bytes, 1);
901 bytes.extend_from_slice(&integer::encode(&[-1]).unwrap());
902 let error = decode(&bytes).unwrap_err();
903 assert!(error.message().contains("negative string length"), "{error}");
904 }
905
906 #[test]
907 fn the_sample_is_spread_across_the_chunk_and_not_taken_from_the_front() {
908 let mut values: Vec<Vec<u8>> = Vec::new();
911 for index in 0..20_000 {
912 let head = if index < 10_000 { "aaaaaaaaaaaaaaaa" } else { "zzzzzzzzzzzzzzzz" };
913 values.push(format!("{head}/{index:08}").into_bytes());
914 }
915 let borrowed = borrow(&values);
916 let sample = sample_of(&borrowed);
917 let first_half = sample.iter().filter(|value| value.starts_with(b"aaaa")).count();
918 let second_half = sample.len() - first_half;
919 assert!(first_half > 0 && second_half > 0, "{first_half} and {second_half}");
920 let bytes = round_trip(&values);
921 let ratio = raw_size(&values) as f64 / bytes.len() as f64;
922 assert!(ratio > 4.0, "{ratio:.2}x");
923 }
924}