Skip to main content

rudb_encoding/
string.rs

1//! The string column, which is offsets, bytes, and the choice between compressing the bytes and
2//! not storing most of them at all.
3//!
4//! ClickBench `hits` is a string dataset before it is anything else. `URL`, `Referer`, `Title` and
5//! the referer derived columns are most of the 20.46 GB DuckDB writes for it, so most of what
6//! `spec/02-the-goal.md` promises on the resource axis has to come out of this file.
7//!
8//! ## The five shapes
9//!
10//! `CONSTANT` when every value is the same. `PLAIN`, which is lengths and raw bytes and is the
11//! baseline the others have to beat. `FSST`, which is a symbol table and the same lengths over
12//! compressed bytes. `DICT`, which is the distinct values and an array of codes. `FRONT`, which is
13//! the length of the prefix each value shares with the one before it and the rest of the value.
14//!
15//! `DICT_FSST` from the section 6.2 table is not a sixth shape. A dictionary's entries are a string
16//! column, and encoding them goes back through the same chooser, so a dictionary whose entries are
17//! FSST compressed is what the chooser produces on its own whenever that is smaller. The same
18//! recursion gives run length encoding of strings for free, because the codes are an integer chunk
19//! and `crate::integer` already knows what to do with a column of long runs.
20//!
21//! ## Why front coding is here
22//!
23//! The whole file measurement in M1 says the chooser produces 11.65 GB for `hits` against Parquet's
24//! 13.76 GB, and that `URL`, `Referer` and `OriginalURL` are 6.11 GB of it, and that on those three
25//! the chooser loses to Parquet's Snappy. The shape it picked on all three was `DICT(FSST[255])`,
26//! so the cascade was working and FSST was still losing.
27//!
28//! The reason is structural. FSST compresses each value on its own against a 255 symbol table, and
29//! a block compressor has the previous few kilobytes of the page to point back into. Two URLs that
30//! share a host and half a path are most of a back reference to each other and are nothing at all
31//! to a symbol table, which can only spend eight bytes of a symbol on the part they share and has
32//! to spend it again on every value. On a sorted dictionary of URLs the value before is the closest
33//! thing in the column to the value in hand, and the bytes they share are the redundancy Snappy was
34//! finding. Front coding is what reaches those bytes, and it composes with everything else here:
35//! the suffixes it leaves behind are a string column and go back through the chooser, so
36//! `DICT(FRONT(FSST))` is a shape the chooser can arrive at without anyone naming it.
37//!
38//! The chain has no restarts, so reading entry `n` means walking from entry zero. That is the right
39//! trade while a dictionary is decoded whole, which is what `decode` does. When something wants one
40//! entry out of a dictionary without materialising the rest, the answer is a restart every so many
41//! entries, and it costs one full value per block.
42//!
43//! ## Lengths, not offsets
44//!
45//! The usual layout is `n + 1` offsets and Arrow does it that way because a slice of an array has
46//! to be free. On disk the offsets are a monotonically increasing sequence whose differences are
47//! the lengths, and the differences are what compress: URL lengths in a real column are a few dozen
48//! distinct values in a narrow band, which the integer cascade turns into a handful of bits each,
49//! while the offsets themselves need enough bits to address the whole chunk. The integer cascade
50//! would find that by choosing DELTA, and storing lengths directly gets to the same place without
51//! spending a level of the cascade on it. Offsets are a prefix sum away and that is a decode time
52//! cost of one add per value.
53//!
54//! ## What is not here
55//!
56//! Nulls. A chunk here is N byte strings and an empty string is a value like any other. Validity is
57//! a bitmap that belongs to the column rather than to the encoding, per `spec/05-storage.md`, and
58//! `ROARING` in the section 6.2 table is what encodes it.
59//!
60//! Shared symbol tables and shared dictionaries across columns, which are section 6.4 and are the
61//! measurement this milestone exists for. Everything here is one column on its own, which is the
62//! baseline they get compared against.
63
64use 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
72/// How deep the recursion goes. A dictionary of a dictionary is not a thing, so this only has to
73/// stop the dictionary's own entries from being dictionary encoded again.
74const MAX_DEPTH: u8 = 2;
75
76/// How little sharing between neighbours is still worth offering front coding for, as one over
77/// this. A twentieth of the column is around where the prefix lengths start paying for themselves,
78/// and below it the candidate is an encode of the whole column that loses.
79const SHARE_DIVISOR: usize = 20;
80
81/// How few bytes is too few to bother looking for repeats in.
82///
83/// The matcher costs a hash table and a pass over the bytes whether it wins or not, and the chooser
84/// is exhaustive, so an ungated candidate is a tax on every string column in the database. Four
85/// kilobytes is about where a 32 KiB window has enough behind it to find anything.
86const LZ_FLOOR: usize = 4096;
87
88/// How many bytes of a column the symbol table is trained on.
89///
90/// The paper trains on about 16 KB. This is four times that, because training happens once per
91/// chunk here rather than once per block, and because the cost of a symbol that is only in the
92/// sample by accident is paid on every value in the chunk.
93pub(crate) const SAMPLE_BYTES: usize = 64 * 1024;
94
95/// What a string chunk is encoded as. The discriminant is the tag byte and is part of the format.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum Kind {
98    /// One value repeated.
99    Constant = 0,
100    /// Lengths and raw bytes.
101    Plain = 1,
102    /// Lengths, a symbol table, and FSST compressed bytes.
103    Fsst = 2,
104    /// The distinct values as a string chunk of their own, and codes into it as an integer chunk.
105    Dict = 3,
106    /// Shared prefix lengths as an integer chunk, and what is left of each value as a string chunk.
107    Front = 4,
108    /// Value lengths, copy lengths and copy offsets as integer chunks, and the bytes no copy
109    /// covered as a string chunk. See the `lz` module for what the matcher does and why it is here.
110    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    /// The name that goes in a report.
131    #[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
144/// Encodes a chunk of strings, choosing whatever comes out smallest.
145///
146/// Every candidate that applies is encoded in full and the smallest is kept, which is what this has
147/// always done and is what every size this crate has reported came out of. [`encode_with`] is the
148/// same thing with the search made swappable.
149///
150/// # Errors
151///
152/// If the chunk is longer than `u32::MAX` values, or if an encoding produces something its own
153/// decoder would not accept.
154pub fn encode(values: &[&[u8]]) -> Result<Vec<u8>> {
155    encode_with(values, &EXHAUSTIVE)
156}
157
158/// [`encode`] with somebody else deciding which candidates are worth encoding in full.
159///
160/// A chooser narrows the list and nothing else. It cannot offer a candidate that does not apply, so
161/// whatever it picks still has to encode the whole chunk and still has to decode, and the worst a
162/// bad one can do is come out bigger than [`encode`] would have.
163///
164/// # Errors
165///
166/// As [`encode`].
167pub fn encode_with(values: &[&[u8]], chooser: &dyn Chooser) -> Result<Vec<u8>> {
168    encode_at(values, 0, chooser)
169}
170
171/// A decoded chunk as one buffer with the values laid end to end, and where each one ends in it.
172///
173/// This is what the decoder builds and [`decode`] is a copy out of it. The cascade is why: a nest
174/// like `FRONT(LZ(FSST))` decodes three levels to produce one, and a level that hands its caller a
175/// `Vec<Vec<u8>>` has allocated once per value and copied every byte it holds. Three levels of that
176/// on a chunk of a thousand URLs is three thousand allocations to produce a thousand strings that
177/// the caller almost always wants back to back anyway.
178///
179/// It also makes the levels cheaper on their own terms. `PLAIN` is one `memcpy` of the whole
180/// payload because the values are already end to end in the file. `FRONT` copies a shared prefix
181/// out of the buffer it is writing into, so the previous value never has to be somewhere else.
182/// `LZ` replays straight into the buffer, which is what its copy offsets meant in the first place.
183#[derive(Debug, Clone, Default, PartialEq, Eq)]
184pub struct Flat {
185    bytes: Vec<u8>,
186    /// Where each value ends, so a value starts where the one before it ended and the last entry
187    /// is the length of `bytes`. Ends rather than offsets because a value is appended and its end
188    /// is what is known at that moment.
189    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    /// Where the value at `index` starts, which is where the one before it ended.
203    fn start(&self, index: usize) -> usize {
204        if index == 0 { 0 } else { self.ends[index - 1] }
205    }
206
207    /// How many values the chunk holds.
208    #[must_use]
209    pub fn len(&self) -> usize {
210        self.ends.len()
211    }
212
213    /// Whether the chunk holds no values at all, which is not the same as holding empty ones.
214    #[must_use]
215    pub fn is_empty(&self) -> bool {
216        self.ends.is_empty()
217    }
218
219    /// The values laid end to end. A caller that already knows the boundaries, which is what a
220    /// global dictionary's offsets are, needs nothing else.
221    #[must_use]
222    pub fn bytes(&self) -> &[u8] {
223        &self.bytes
224    }
225
226    /// The value at `index`, or `None` past the end.
227    #[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    /// Every value in order.
234    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    /// The buffer on its own, for a caller that wanted the bytes rather than the values.
244    #[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
260/// Decodes a chunk written by [`encode`] without taking it apart into a value each.
261///
262/// # Errors
263///
264/// As [`decode`].
265pub 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
277/// Decodes a chunk that sits at the front of a longer buffer, and says how many bytes it took.
278///
279/// A column group holds one of these per column, and the decoder on that side cannot know where
280/// one ends until it has been read.
281///
282/// # Errors
283///
284/// As [`decode`], except that trailing bytes are what the caller asked about rather than an error.
285pub 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
291/// [`describe`] over a chunk at the front of a longer buffer, and how many bytes it took.
292///
293/// # Errors
294///
295/// As [`decode_prefix`].
296pub 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
302/// Decodes a chunk written by [`encode`].
303///
304/// # Errors
305///
306/// If the bytes are truncated, carry an unknown tag, or describe a chunk whose parts disagree.
307pub fn decode(bytes: &[u8]) -> Result<Vec<Vec<u8>>> {
308    Ok(decode_flat(bytes)?.into_values())
309}
310
311/// The size of every candidate that applies, for a report that wants to say what was chosen over
312/// what.
313///
314/// # Errors
315///
316/// As [`encode`].
317pub 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/// Which candidates [`encode`] would try on this chunk, in the order it tries them.
328///
329/// The chooser is exhaustive, so this is also the list of encodes it pays for to return one of
330/// them. A caller measuring where the encode time goes needs the list separately from the sizes,
331/// because a candidate that is offered and turns out not to apply still costs whatever it spent
332/// finding that out.
333#[must_use]
334pub fn offered(values: &[&[u8]]) -> Vec<Kind> {
335    candidates(values, 0)
336}
337
338/// One candidate on its own, which is what the chooser calls once per entry in [`offered`].
339///
340/// `None` when the encoding does not apply, which is what the chooser treats as a candidate that
341/// did not run rather than as a failure. This is here so that the time the chooser spends can be
342/// attributed to the candidate that spent it, which is the measurement F2 wants before anybody
343/// replaces the exhaustive search with a sampled one. It is not how a writer encodes a chunk:
344/// [`encode`] is, and picking a kind by hand gives up the only thing the chooser is for.
345///
346/// # Errors
347///
348/// As [`encode`].
349pub fn encode_only(kind: Kind, values: &[&[u8]]) -> Result<Option<Vec<u8>>> {
350    encode_as(kind, values, 0, &EXHAUSTIVE)
351}
352
353/// How big one candidate comes out, which is all a sampling chooser needs from it.
354///
355/// The bytes are thrown away, so this says nothing [`encode_only`] does not. It is `pub(crate)` and
356/// separate so that the sampler in [`crate::chooser`] is not handing back buffers it will not read.
357pub(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
361/// The shape a chunk was encoded as, as a line of text like `DICT(FSST, RLE(...))`.
362///
363/// # Errors
364///
365/// As [`decode`].
366pub 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
406/// How many bytes each value shares with the value before it, added up.
407///
408/// This is a full pass over the column, and it is here rather than on a sample because it is byte
409/// comparisons that stop at the first difference, which on a column with nothing to share stops
410/// immediately. Against training a symbol table and compressing the whole column, which is what
411/// offering the candidate would cost, it is not worth sampling.
412fn 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
420/// Every value split into the bytes it shares with the value before it and the bytes it does not.
421///
422/// The suffixes point into the values, so this costs the prefix lengths and nothing else. It is
423/// shared with [`crate::multi`], which front codes a column before compressing it against a symbol
424/// table that belongs to the whole group.
425pub(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
439/// The other half. The suffixes are consumed because the values are built out of them.
440///
441/// # Errors
442///
443/// If a prefix is negative or is longer than the value it is a prefix of, which is what a corrupt
444/// or hand written chunk looks like from here.
445pub(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            // One copy of the whole payload rather than one a value, which the file already laid
566            // out end to end and which is the layout wanted back.
567            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            // The compressed total is what the buffer has to hold and it is also the only sane
583            // guess at the decompressed one, so it is checked before it is believed.
584            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            // The shared prefix is copied out of the buffer being written into, so a value never
629            // has to exist anywhere but where it belongs.
630            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            // The copies point back into the bytes already replayed, which is the buffer the values
672            // are going into, so the replay is the decode and there is nothing to cut up after it.
673            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
732/// The shape of the length array and the lengths themselves, because a describe has to walk past
733/// the payload to leave the reader where the next chunk starts and the payload size is the sum of
734/// the lengths.
735fn 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
762/// How long the values add up to, refusing a length array that adds up to more than memory.
763///
764/// A truncated chunk used to be caught by the read of the value that ran off the end. Reading the
765/// payload in one go means the total has to be trusted before the read rather than after it, and a
766/// corrupt length array is the only thing that could overflow it.
767fn 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
774/// Reads one nested integer chunk. The integer decoder wants a slice of exactly its own chunk and
775/// the reader does not know how long that is, so it decodes from the rest of the buffer and is told
776/// afterwards how much it used.
777fn 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
789/// A sample of the column spread across the whole of it, taken at random skips rather than at a
790/// fixed stride.
791///
792/// Section 6.3 makes the point about choosing an encoding from a sample and it applies at least as
793/// much to training a symbol table. Column data is frequently sorted or clustered, so the first
794/// 64 KB of a URL column is the hosts that sort first and a table trained on it escapes most of the
795/// rest of the column.
796///
797/// The skips are random rather than fixed because a fixed stride aliases. Column data is also
798/// frequently periodic, and a stride that shares a factor with the period samples one phase of it
799/// and never sees the others. That is not a hypothetical: the first version of this took every
800/// `n`th value, and on a test column whose values cycle with a period that the stride happened to
801/// divide, the table it trained was 3.4 times worse than one trained on the whole column, because
802/// it learned eight byte symbols that only line up with the phase it saw and had no shorter symbols
803/// left to fall back on.
804///
805/// The generator is a fixed seed xorshift, so the sample is a function of the column and encoding
806/// the same values twice produces the same bytes.
807pub(crate) fn sample_of<'a>(values: &[&'a [u8]]) -> Vec<&'a [u8]> {
808    sample_bytes_of(values, SAMPLE_BYTES)
809}
810
811/// [`sample_of`] with the byte budget spelled out, for a caller training one table over several
812/// columns that has to split the budget between them.
813pub(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
834/// The distinct values in sorted order and the code of every value, in one pass over one sort.
835///
836/// The dictionary is sorted for the same reason the integer one is: an ordered dictionary turns a
837/// range predicate into a code range rather than a code set, and front coding over the entries needs
838/// them sorted anyway.
839///
840/// It sorts a permutation of indices rather than the values, which is the whole point. Sorting the
841/// values means copying every one of them onto the heap first, and the codes then have to be found
842/// by searching the dictionary back for each value, which is a binary search of string comparisons
843/// per row. Walking the permutation gives the codes away for free, because the position a value
844/// sorted to is the position its code was assigned at.
845fn 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
860/// Whether any value appears twice, which is the only thing the candidate list wants to know.
861///
862/// This used to build the whole sorted dictionary and compare its length against the input, which
863/// is a copy of the chunk and a sort of it paid on every chunk at every level whether the dictionary
864/// was ever encoded or not. It is a linear probe over hashes instead: expected O(n), no allocation
865/// per value, and it stops at the first duplicate it finds, which on a column with any repetition at
866/// all is immediately.
867///
868/// A hash collision is resolved by comparing the bytes, so the answer is exact rather than probable.
869fn 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
892/// FNV-1a over the bytes, eight at a time.
893///
894/// Good enough for a table that verifies every hit, and it is not part of the format, so nothing
895/// depends on which hash this is. Eight bytes at a time because a URL column is long values and a
896/// byte at a time over a hundred bytes of every one of 122,880 rows is the loop this is here to
897/// avoid.
898fn 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    /// The same values with a scrambled identifier stuck on the front of each, for the tests that
936    /// need neighbouring values to have nothing in common. Shuffling the order is not enough,
937    /// because two URLs picked at random still agree on a scheme and often on a host.
938    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    /// The flat form holds the same values and lays them out the way a caller with its own offsets
965    /// expects. Called from [`round_trip`], so every shape any test in here reaches is checked.
966    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        // round_trip only sees the shape the chooser picked, which on any one column is one of the
982        // six. This walks all of them, so PLAIN reading its payload in one go and FRONT copying a
983        // prefix out of the buffer it is filling are both covered on data they apply to.
984        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        // The prefix chain is the one place the flat decoder reads back out of the buffer it is
1003        // filling, so a prefix longer than the value before it is what would hand back somebody
1004        // else's bytes rather than fail. Built by hand because no encoder produces one.
1005        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        // The two things the dictionary path has to get right, and the reason it is one function
1018        // now rather than a sort followed by a binary search per row.
1019        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        // One repeat at the far end, so a check that gave up early would miss it.
1042        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        // The hash is eight bytes at a time and the table verifies every hit, so this is the case
1054        // that says the verify is really there rather than the hash being trusted.
1055        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        // `offered` and `encode_only` are what `cargo xtask encode` splits the chooser's seconds
1067        // with, so they have to describe the chooser that actually runs rather than a second copy
1068        // of its rules that drifts. This is the assertion that keeps the two the same thing: walk
1069        // the list, encode each one alone, and the smallest has to be byte for byte what `encode`
1070        // came back with.
1071        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        // Every value distinct, so a dictionary is the values plus an index and cannot win, and
1108        // every value starts with an identifier of its own, so neighbours share nothing and front
1109        // coding cannot win either. This used to be the case that fell back to FSST, on the
1110        // reasoning that a symbol table was the only thing that could reach repeated vocabulary
1111        // with no structure around it. That reasoning was wrong and #575 is the measurement: the
1112        // vocabulary repeats at a distance, and a match finder reaches distance where a 255 symbol
1113        // table of at most eight bytes each does not.
1114        let values = keyed(urls(20_000));
1115        let bytes = round_trip(&values);
1116        assert_eq!(kind_of(&bytes), Kind::Lz);
1117
1118        // Against the encoding that used to win, on the same values, so the claim is a comparison
1119        // and not just a label.
1120        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        // Eleven bytes of every value are the identifier and a separator and nothing compresses
1125        // them, so the ratio here is lower than the one FSST gets on the URLs on their own.
1126        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        // This column is periodic and its period is what a fixed stride would have divided. The
1133        // sample has to see all of it, because a table trained on one phase learns eight byte
1134        // symbols that only line up with that phase and has nothing shorter to fall back on. The
1135        // measured cost of getting this wrong was 3.4 times the compressed size.
1136        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        // Three hosts and four paths, and the sample has to contain all twelve of the combinations.
1150        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        // Training on a twentieth of the column is allowed to cost something. It is not allowed to
1160        // cost a factor.
1161        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        // The DICT_FSST row of the section 6.2 table, which is not an encoding of its own here: it
1172        // is a dictionary whose entries went back through the chooser. What the entries then get
1173        // is whatever wins on them, and since #575 that is the match finder rather than front
1174        // coding with the leftovers FSST compressed. The point of the test is unchanged: nobody
1175        // named the shape and the chooser arrived at it.
1176        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        // A dictionary makes the codes an integer chunk, and the integer chunk knows what to do
1190        // with runs, so run length encoding of strings falls out of the recursion.
1191        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        // The case where nothing works. It has to land on PLAIN or on an FSST that is not much
1205        // worse, rather than on a dictionary of every value in the column.
1206        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        // Every value is 24 bytes, so the lengths are a constant chunk and cost 13 bytes for the
1226        // whole column. Offsets would be 100,000 increasing integers.
1227        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        // The M1 finding, in a test. Sorted URLs share a host and most of a path with the URL next
1303        // to them, FSST cannot reach those bytes because it compresses each value on its own, and
1304        // front coding is the shape that reaches them.
1305        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        // The candidate costs an encode of the whole column, so a column whose neighbours have
1324        // nothing in common must not be paying for it.
1325        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        // A sorted column whose first 64 KB says nothing about the rest of it. If the sample were
1374        // the front, the table would learn `aaaa` and escape every `zzzz`.
1375        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}