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 std::time::Instant;
65
66use rudb_common::{Error, Result};
67
68use crate::chooser::{Chooser, EXHAUSTIVE, Settled};
69use crate::fsst::{MAX_SYMBOL_LEN, SymbolTable};
70use crate::integer;
71use crate::lz;
72use crate::reader::Reader;
73use crate::sequence::Sequence;
74use crate::tally::{self, Family};
75
76/// How deep the recursion goes. A dictionary of a dictionary is not a thing, so this only has to
77/// stop the dictionary's own entries from being dictionary encoded again.
78const MAX_DEPTH: u8 = 2;
79
80/// How little sharing between neighbours is still worth offering front coding for, as one over
81/// this. A twentieth of the column is around where the prefix lengths start paying for themselves,
82/// and below it the candidate is an encode of the whole column that loses.
83const SHARE_DIVISOR: usize = 20;
84
85/// How few bytes is too few to bother looking for repeats in.
86///
87/// The matcher costs a hash table and a pass over the bytes whether it wins or not, and the chooser
88/// is exhaustive, so an ungated candidate is a tax on every string column in the database. Four
89/// kilobytes is about where a 32 KiB window has enough behind it to find anything.
90const LZ_FLOOR: usize = 4096;
91
92/// How many bytes of a column the symbol table is trained on.
93///
94/// The paper trains on about 16 KB. This is four times that, because training happens once per
95/// chunk here rather than once per block, and because the cost of a symbol that is only in the
96/// sample by accident is paid on every value in the chunk.
97pub(crate) const SAMPLE_BYTES: usize = 64 * 1024;
98
99/// What a string chunk is encoded as. The discriminant is the tag byte and is part of the format.
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum Kind {
102    /// One value repeated.
103    Constant = 0,
104    /// Lengths and raw bytes.
105    Plain = 1,
106    /// Lengths, a symbol table, and FSST compressed bytes.
107    Fsst = 2,
108    /// The distinct values as a string chunk of their own, and codes into it as an integer chunk.
109    Dict = 3,
110    /// Shared prefix lengths as an integer chunk, and what is left of each value as a string chunk.
111    Front = 4,
112    /// Value lengths, copy lengths and copy offsets as integer chunks, and the bytes no copy
113    /// covered as a string chunk. See the `lz` module for what the matcher does and why it is here.
114    Lz = 5,
115}
116
117impl Kind {
118    /// Every kind, in tag order.
119    pub const ALL: [Self; 6] =
120        [Self::Constant, Self::Plain, Self::Fsst, Self::Dict, Self::Front, Self::Lz];
121
122    fn tag(self) -> u8 {
123        self as u8
124    }
125
126    fn from_tag(tag: u8) -> Result<Self> {
127        match tag {
128            0 => Ok(Self::Constant),
129            1 => Ok(Self::Plain),
130            2 => Ok(Self::Fsst),
131            3 => Ok(Self::Dict),
132            4 => Ok(Self::Front),
133            5 => Ok(Self::Lz),
134            other => Err(Error::internal(format!("unknown string encoding tag {other}"))),
135        }
136    }
137
138    /// The name that goes in a report.
139    #[must_use]
140    pub fn name(self) -> &'static str {
141        match self {
142            Self::Constant => "CONSTANT",
143            Self::Plain => "PLAIN",
144            Self::Fsst => "FSST",
145            Self::Dict => "DICT",
146            Self::Front => "FRONT",
147            Self::Lz => "LZ",
148        }
149    }
150}
151
152/// Encodes a chunk of strings, choosing whatever comes out smallest.
153///
154/// Every candidate that applies is encoded in full and the smallest is kept, which is what this has
155/// always done and is what every size this crate has reported came out of. [`encode_with`] is the
156/// same thing with the search made swappable.
157///
158/// # Errors
159///
160/// If the chunk is longer than `u32::MAX` values, or if an encoding produces something its own
161/// decoder would not accept.
162pub fn encode(values: &[&[u8]]) -> Result<Vec<u8>> {
163    encode_with(values, &EXHAUSTIVE)
164}
165
166/// [`encode`] with somebody else deciding which candidates are worth encoding in full.
167///
168/// A chooser narrows the list and nothing else. It cannot offer a candidate that does not apply, so
169/// whatever it picks still has to encode the whole chunk and still has to decode, and the worst a
170/// bad one can do is come out bigger than [`encode`] would have.
171///
172/// # Errors
173///
174/// As [`encode`].
175pub fn encode_with(values: &[&[u8]], chooser: &dyn Chooser) -> Result<Vec<u8>> {
176    encode_at(values, 0, chooser)
177}
178
179/// A decoded chunk as one buffer with the values laid end to end, and where each one ends in it.
180///
181/// This is what the decoder builds and [`decode`] is a copy out of it. The cascade is why: a nest
182/// like `FRONT(LZ(FSST))` decodes three levels to produce one, and a level that hands its caller a
183/// `Vec<Vec<u8>>` has allocated once per value and copied every byte it holds. Three levels of that
184/// on a chunk of a thousand URLs is three thousand allocations to produce a thousand strings that
185/// the caller almost always wants back to back anyway.
186///
187/// It also makes the levels cheaper on their own terms. `PLAIN` is one `memcpy` of the whole
188/// payload because the values are already end to end in the file. `FRONT` copies a shared prefix
189/// out of the buffer it is writing into, so the previous value never has to be somewhere else.
190/// `LZ` replays straight into the buffer, which is what its copy offsets meant in the first place.
191#[derive(Debug, Clone, Default, PartialEq, Eq)]
192pub struct Flat {
193    bytes: Vec<u8>,
194    /// Where each value ends, so a value starts where the one before it ended and the last entry
195    /// is the length of `bytes`. Ends rather than offsets because a value is appended and its end
196    /// is what is known at that moment.
197    ends: Vec<usize>,
198}
199
200impl Flat {
201    fn with_capacity(count: usize, bytes: usize) -> Self {
202        Self { bytes: Vec::with_capacity(bytes), ends: Vec::with_capacity(count) }
203    }
204
205    fn push(&mut self, value: &[u8]) {
206        self.bytes.extend_from_slice(value);
207        self.ends.push(self.bytes.len());
208    }
209
210    /// Where the value at `index` starts, which is where the one before it ended.
211    fn start(&self, index: usize) -> usize {
212        if index == 0 { 0 } else { self.ends[index - 1] }
213    }
214
215    /// How many values the chunk holds.
216    #[must_use]
217    pub fn len(&self) -> usize {
218        self.ends.len()
219    }
220
221    /// Whether the chunk holds no values at all, which is not the same as holding empty ones.
222    #[must_use]
223    pub fn is_empty(&self) -> bool {
224        self.ends.is_empty()
225    }
226
227    /// The values laid end to end. A caller that already knows the boundaries, which is what a
228    /// global dictionary's offsets are, needs nothing else.
229    #[must_use]
230    pub fn bytes(&self) -> &[u8] {
231        &self.bytes
232    }
233
234    /// The value at `index`, or `None` past the end.
235    #[must_use]
236    pub fn get(&self, index: usize) -> Option<&[u8]> {
237        let end = *self.ends.get(index)?;
238        self.bytes.get(self.start(index)..end)
239    }
240
241    /// Every value in order.
242    pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
243        let mut at = 0;
244        self.ends.iter().map(move |end| {
245            let value = self.bytes.get(at..*end).unwrap_or_default();
246            at = *end;
247            value
248        })
249    }
250
251    /// The buffer on its own, for a caller that wanted the bytes rather than the values.
252    #[must_use]
253    pub fn into_bytes(self) -> Vec<u8> {
254        self.bytes
255    }
256
257    /// The buffer and the ends that divide it, for a caller building its own layout over them.
258    ///
259    /// [`into_bytes`](Self::into_bytes) is enough for a caller that already knows where the values
260    /// end, which is what a global dictionary's stored offsets are. A caller that does not know has
261    /// only [`iter`](Self::iter), and walking that to build a run of boundaries copies out numbers
262    /// the chunk already holds. This hands both halves over and keeps the one allocation each.
263    #[must_use]
264    pub fn into_parts(self) -> (Vec<u8>, Vec<usize>) {
265        (self.bytes, self.ends)
266    }
267
268    fn into_values(self) -> Vec<Vec<u8>> {
269        let mut values = Vec::with_capacity(self.len());
270        let mut at = 0;
271        for end in &self.ends {
272            values.push(self.bytes[at..*end].to_vec());
273            at = *end;
274        }
275        values
276    }
277}
278
279/// Decodes a chunk written by [`encode`] without taking it apart into a value each.
280///
281/// # Errors
282///
283/// As [`decode`].
284pub fn decode_flat(bytes: &[u8]) -> Result<Flat> {
285    let mut reader = Reader::new(bytes);
286    let flat = decode_chunk(&mut reader)?;
287    if reader.remaining() != 0 {
288        return Err(Error::internal(format!(
289            "{} bytes left over after decoding a string chunk",
290            reader.remaining()
291        )));
292    }
293    Ok(flat)
294}
295
296/// Whether each value of a chunk written by [`encode`] holds `sequence`'s pieces in order, answered
297/// without decompressing it, or `None` for a chunk that is not compressed.
298///
299/// A compressed chunk is walked a code at a time, see [`Sequence`], and nothing is written out. Any
300/// other shape is cheap to decode already and the caller reads it the usual way. A null is stored as
301/// an empty value here, so the caller still has to take the nulls out.
302///
303/// # Errors
304///
305/// As [`decode`].
306pub fn holds_in(bytes: &[u8], sequence: &Sequence) -> Result<Option<Vec<bool>>> {
307    holds_in_where(bytes, sequence, |_| true)
308}
309
310/// [`holds_in`], walking only the values `maybe` does not rule out and answering no for the rest.
311///
312/// For a caller that holds a sketch of each value, see [`crate::sequence::grams`]. A value ruled out
313/// is stepped over by its length and never walked.
314///
315/// # Errors
316///
317/// As [`decode`].
318pub fn holds_in_where(
319    bytes: &[u8],
320    sequence: &Sequence,
321    mut maybe: impl FnMut(usize) -> bool,
322) -> Result<Option<Vec<bool>>> {
323    if bytes.first() != Some(&Kind::Fsst.tag()) {
324        return Ok(None);
325    }
326    let mut reader = Reader::new(bytes);
327    reader.u8()?;
328    let count = reader.u32()? as usize;
329    let runs = read_compressed(&mut reader, count)?;
330    let mut coded = sequence.over(&runs.table);
331    let mut held = Vec::with_capacity(count);
332    let mut payload = runs.payload;
333    for &run in &runs.lengths {
334        let Some((codes, rest)) = payload.split_at_checked(run) else {
335            return Err(Error::internal("a compressed run is past the end of its chunk"));
336        };
337        payload = rest;
338        let row = held.len();
339        held.push(maybe(row) && coded.holds(codes)?);
340    }
341    if reader.remaining() != 0 {
342        return Err(Error::internal(format!(
343            "{} bytes left over after decoding a string chunk",
344            reader.remaining()
345        )));
346    }
347    Ok(Some(held))
348}
349
350/// Decodes only the values at `positions` of a chunk written by [`encode`], in that order.
351///
352/// A compressed chunk keeps every run's length, so the runs that are not wanted are stepped over
353/// by adding their lengths and never decompressed. That is what a scan wants when a join has
354/// already said which rows it keeps: in TPC-H q10 the customer scan keeps a quarter of its rows,
355/// and decompressing the other three quarters of four string columns was most of what it did. The
356/// other shapes are decoded whole and picked from, which costs what reading them always did.
357///
358/// # Errors
359///
360/// As [`decode`], and if the positions do not rise or one is past the end of the chunk.
361pub fn decode_flat_at(bytes: &[u8], positions: &[u32]) -> Result<Flat> {
362    if positions.windows(2).any(|pair| pair[0] >= pair[1]) {
363        return Err(Error::internal("the positions to decode do not rise"));
364    }
365    let mut reader = Reader::new(bytes);
366    let flat = if bytes.first() == Some(&Kind::Fsst.tag()) {
367        reader.u8()?;
368        let count = reader.u32()? as usize;
369        let runs = read_compressed(&mut reader, count)?;
370        let mut flat = Flat::with_capacity(positions.len(), runs.payload.len());
371        let mut at = 0;
372        let mut next = 0;
373        for &position in positions {
374            let position = position as usize;
375            if position >= count {
376                return Err(Error::internal(format!("value {position} is not in the chunk")));
377            }
378            at += runs.lengths[next..position].iter().sum::<usize>();
379            runs.run_into(position, &mut at, &mut flat.bytes)?;
380            flat.ends.push(flat.bytes.len());
381            next = position + 1;
382        }
383        flat
384    } else {
385        let whole = decode_chunk(&mut reader)?;
386        let mut flat = Flat::with_capacity(positions.len(), 0);
387        for &position in positions {
388            let value = whole
389                .get(position as usize)
390                .ok_or_else(|| Error::internal(format!("value {position} is not in the chunk")))?;
391            flat.push(value);
392        }
393        flat
394    };
395    if reader.remaining() != 0 {
396        return Err(Error::internal(format!(
397            "{} bytes left over after decoding a string chunk",
398            reader.remaining()
399        )));
400    }
401    Ok(flat)
402}
403
404/// Decodes a chunk that sits at the front of a longer buffer, and says how many bytes it took.
405///
406/// A column group holds one of these per column, and the decoder on that side cannot know where
407/// one ends until it has been read.
408///
409/// # Errors
410///
411/// As [`decode`], except that trailing bytes are what the caller asked about rather than an error.
412pub fn decode_prefix(bytes: &[u8]) -> Result<(Vec<Vec<u8>>, usize)> {
413    let mut reader = Reader::new(bytes);
414    let values = decode_chunk(&mut reader)?;
415    Ok((values.into_values(), reader.used()))
416}
417
418/// [`describe`] over a chunk at the front of a longer buffer, and how many bytes it took.
419///
420/// # Errors
421///
422/// As [`decode_prefix`].
423pub fn describe_prefix(bytes: &[u8]) -> Result<(String, usize)> {
424    let mut reader = Reader::new(bytes);
425    let text = describe_chunk(&mut reader)?;
426    Ok((text, reader.used()))
427}
428
429/// Decodes a chunk written by [`encode`].
430///
431/// # Errors
432///
433/// If the bytes are truncated, carry an unknown tag, or describe a chunk whose parts disagree.
434pub fn decode(bytes: &[u8]) -> Result<Vec<Vec<u8>>> {
435    Ok(decode_flat(bytes)?.into_values())
436}
437
438/// The size of every candidate that applies, for a report that wants to say what was chosen over
439/// what.
440///
441/// # Errors
442///
443/// As [`encode`].
444pub fn candidate_sizes(values: &[&[u8]]) -> Result<Vec<(Kind, usize)>> {
445    let mut sizes = Vec::new();
446    for kind in candidates(values, 0) {
447        if let Some(bytes) = encode_as(kind, values, 0, &EXHAUSTIVE)? {
448            sizes.push((kind, bytes.len()));
449        }
450    }
451    Ok(sizes)
452}
453
454/// Which candidates [`encode`] would try on this chunk, in the order it tries them.
455///
456/// The chooser is exhaustive, so this is also the list of encodes it pays for to return one of
457/// them. A caller measuring where the encode time goes needs the list separately from the sizes,
458/// because a candidate that is offered and turns out not to apply still costs whatever it spent
459/// finding that out.
460#[must_use]
461pub fn offered(values: &[&[u8]]) -> Vec<Kind> {
462    candidates(values, 0)
463}
464
465/// One candidate on its own, which is what the chooser calls once per entry in [`offered`].
466///
467/// `None` when the encoding does not apply, which is what the chooser treats as a candidate that
468/// did not run rather than as a failure. This is here so that the time the chooser spends can be
469/// attributed to the candidate that spent it, which is the measurement F2 wants before anybody
470/// replaces the exhaustive search with a sampled one. It is not how a writer encodes a chunk:
471/// [`encode`] is, and picking a kind by hand gives up the only thing the chooser is for.
472///
473/// # Errors
474///
475/// As [`encode`].
476pub fn encode_only(kind: Kind, values: &[&[u8]]) -> Result<Option<Vec<u8>>> {
477    encode_as(kind, values, 0, &EXHAUSTIVE)
478}
479
480/// A shape that is FSST alone, against one table trained on a sample of `values`.
481///
482/// For a writer that compresses page after page of one column with FSST and nothing else. Training
483/// is most of what FSST costs on a page of a thousand short values, and a page of `l_comment` trained
484/// a table of its own, which on a TPC-H `lineitem` load from CSV was six percent of every cycle. The
485/// table a page trains is much the same as the one the page before it trained, so the writer trains
486/// one here, hands it to [`encode_fsst`] for the pages after, and trains again when it stops paying.
487///
488/// The time is counted as choosing, the way the rest of the time spent deciding is.
489#[must_use]
490pub fn fsst_shape(values: &[&[u8]]) -> Settled {
491    let started = Instant::now();
492    let table = SymbolTable::train(&sample_of(values));
493    tally::chose(Family::String, started);
494    Settled::new(vec![Kind::Fsst], Vec::new()).with_symbols(0, table)
495}
496
497/// `values` as one FSST chunk against the table in `shape`, which [`fsst_shape`] made.
498///
499/// `None` when the table is empty, which is what a sample with nothing worth a symbol trains. This
500/// is counted as an offer of FSST and, when it comes out, as kept, so that the pages a writer
501/// compresses this way show up in `rudb_codec_metrics()` with the rest.
502///
503/// # Errors
504///
505/// As [`encode`].
506pub fn encode_fsst(values: &[&[u8]], shape: &Settled) -> Result<Option<Vec<u8>>> {
507    let out =
508        tally::offer(Family::String, Kind::Fsst.tag(), || encode_as(Kind::Fsst, values, 0, shape))?;
509    if out.is_some() {
510        tally::kept(Family::String, Kind::Fsst.tag());
511    }
512    Ok(out)
513}
514
515/// How big one candidate comes out, which is all a sampling chooser needs from it.
516///
517/// The bytes are thrown away, so this says nothing [`encode_only`] does not. It is `pub(crate)` and
518/// separate so that the sampler in [`crate::chooser`] is not handing back buffers it will not read.
519pub(crate) fn size_as(kind: Kind, values: &[&[u8]], depth: u8) -> Result<Option<usize>> {
520    Ok(encode_as(kind, values, depth, &EXHAUSTIVE)?.map(|bytes| bytes.len()))
521}
522
523/// The shape a chunk was encoded as, as a line of text like `DICT(FSST, RLE(...))`.
524///
525/// # Errors
526///
527/// As [`decode`].
528pub fn describe(bytes: &[u8]) -> Result<String> {
529    let mut reader = Reader::new(bytes);
530    describe_chunk(&mut reader)
531}
532
533/// `shape` with one symbol table for the whole column, trained on what reaches FSST in `blocks`.
534///
535/// A settled shape is used for thousands of blocks of one column, and every block that tries FSST
536/// trains its own table. On ClickBench `hits` that was 35 seconds of a 150 second load, most of it
537/// on the literals `FRONT` then `LZ` leaves behind in `URL` and `Referer`, where the table comes
538/// out much the same block after block. So the blocks the shape was settled on are taken down the
539/// shape's levels here, the values that arrive at the FSST level are sampled together, and the
540/// table trained on them is handed to every block through [`Chooser::symbols`].
541///
542/// A shape that ends in `PLAIN` before any FSST level comes back as it was. So does one whose
543/// table comes out empty, which leaves each block to train its own as before.
544#[must_use]
545pub fn with_symbols(shape: Settled, blocks: &[Vec<&[u8]>]) -> Settled {
546    let kinds = shape.strings();
547    let Some(depth) =
548        (0..=kinds.len()).find(|&at| matches!(kinds.get(at), Some(Kind::Fsst) | None))
549    else {
550        return shape;
551    };
552    let leads =
553        kinds[..depth].iter().all(|kind| matches!(kind, Kind::Front | Kind::Lz | Kind::Dict));
554    if !leads || depth > usize::from(MAX_DEPTH) {
555        return shape;
556    }
557    let mut reached: Vec<Vec<u8>> = Vec::new();
558    for block in blocks {
559        let mut values: Vec<Vec<u8>> = block.iter().map(|value| value.to_vec()).collect();
560        for kind in &kinds[..depth] {
561            let refs: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
562            values = match kind {
563                Kind::Front => front_code(&refs).1.into_iter().map(<[u8]>::to_vec).collect(),
564                Kind::Dict => dictionary_of(&refs).0.into_iter().map(<[u8]>::to_vec).collect(),
565                _ => {
566                    let joined = refs.concat();
567                    lz::tokens_of(&joined).literals.into_iter().map(<[u8]>::to_vec).collect()
568                }
569            };
570        }
571        reached.extend(values);
572    }
573    let refs: Vec<&[u8]> = reached.iter().map(Vec::as_slice).collect();
574    let table = SymbolTable::train(&sample_of(&refs));
575    if table.is_empty() {
576        return shape;
577    }
578    shape.with_symbols(depth as u8, table)
579}
580
581fn encode_at(values: &[&[u8]], depth: u8, chooser: &dyn Chooser) -> Result<Vec<u8>> {
582    let started = Instant::now();
583    let offered = candidates(values, depth);
584    let narrowed = chooser.narrow_strings(values, &offered, depth);
585    // Only the top level is counted, so that a cascade's time is counted once. See `tally`.
586    let counted = depth == 0;
587    if counted {
588        tally::chose(Family::String, started);
589    }
590    let mut best: Option<(Kind, Vec<u8>)> = None;
591    for kind in narrowed {
592        let encoded = if counted {
593            tally::offer(Family::String, kind.tag(), || encode_as(kind, values, depth, chooser))?
594        } else {
595            encode_as(kind, values, depth, chooser)?
596        };
597        let Some(bytes) = encoded else {
598            continue;
599        };
600        if best.as_ref().is_none_or(|(_, current)| bytes.len() < current.len()) {
601            best = Some((kind, bytes));
602        }
603    }
604    let (kind, bytes) =
605        best.ok_or_else(|| Error::internal("no string encoding applied to the chunk"))?;
606    if counted {
607        tally::kept(Family::String, kind.tag());
608    }
609    Ok(bytes)
610}
611
612fn candidates(values: &[&[u8]], depth: u8) -> Vec<Kind> {
613    let mut kinds = vec![Kind::Plain];
614    if values.is_empty() {
615        return kinds;
616    }
617    if values.iter().all(|value| *value == values[0]) {
618        return vec![Kind::Constant];
619    }
620    kinds.push(Kind::Fsst);
621    if depth < MAX_DEPTH && has_duplicates(values) {
622        kinds.push(Kind::Dict);
623    }
624    if depth < MAX_DEPTH && sharing_of(values) >= total_len(values) / SHARE_DIVISOR {
625        kinds.push(Kind::Front);
626    }
627    if depth < MAX_DEPTH && total_len(values) >= LZ_FLOOR {
628        kinds.push(Kind::Lz);
629    }
630    kinds
631}
632
633/// How many bytes each value shares with the value before it, added up.
634///
635/// This is a full pass over the column, and it is here rather than on a sample because it is byte
636/// comparisons that stop at the first difference, which on a column with nothing to share stops
637/// immediately. Against training a symbol table and compressing the whole column, which is what
638/// offering the candidate would cost, it is not worth sampling.
639fn sharing_of(values: &[&[u8]]) -> usize {
640    let mut shared = 0;
641    for pair in values.windows(2) {
642        shared += shared_prefix(pair[0], pair[1]);
643    }
644    shared
645}
646
647/// Every value split into the bytes it shares with the value before it and the bytes it does not.
648///
649/// The suffixes point into the values, so this costs the prefix lengths and nothing else. It is
650/// shared with [`crate::multi`], which front codes a column before compressing it against a symbol
651/// table that belongs to the whole group.
652pub(crate) fn front_code<'a>(values: &[&'a [u8]]) -> (Vec<i64>, Vec<&'a [u8]>) {
653    let mut prefixes = Vec::with_capacity(values.len());
654    let mut suffixes: Vec<&'a [u8]> = Vec::with_capacity(values.len());
655    let mut previous: &[u8] = b"";
656    for value in values {
657        let value: &'a [u8] = value;
658        let shared = shared_prefix(previous, value);
659        prefixes.push(shared as i64);
660        suffixes.push(&value[shared..]);
661        previous = value;
662    }
663    (prefixes, suffixes)
664}
665
666/// The other half. The suffixes are consumed because the values are built out of them.
667///
668/// # Errors
669///
670/// If a prefix is negative or is longer than the value it is a prefix of, which is what a corrupt
671/// or hand written chunk looks like from here.
672pub(crate) fn front_decode(prefixes: &[i64], suffixes: Vec<Vec<u8>>) -> Result<Vec<Vec<u8>>> {
673    let mut values: Vec<Vec<u8>> = Vec::with_capacity(suffixes.len());
674    for (index, suffix) in suffixes.into_iter().enumerate() {
675        let shared = usize::try_from(prefixes[index])
676            .map_err(|_| Error::internal("a negative shared prefix length"))?;
677        let previous: &[u8] = if index == 0 { b"" } else { &values[index - 1] };
678        if shared > previous.len() {
679            return Err(Error::internal(format!(
680                "a value shares {shared} bytes with a value {} bytes long",
681                previous.len()
682            )));
683        }
684        let mut value = Vec::with_capacity(shared + suffix.len());
685        value.extend_from_slice(&previous[..shared]);
686        value.extend_from_slice(&suffix);
687        values.push(value);
688    }
689    Ok(values)
690}
691
692fn shared_prefix(previous: &[u8], value: &[u8]) -> usize {
693    let limit = previous.len().min(value.len());
694    let mut shared = 0;
695    while shared < limit && previous[shared] == value[shared] {
696        shared += 1;
697    }
698    shared
699}
700
701fn total_len(values: &[&[u8]]) -> usize {
702    values.iter().map(|value| value.len()).sum()
703}
704
705fn encode_as(
706    kind: Kind,
707    values: &[&[u8]],
708    depth: u8,
709    chooser: &dyn Chooser,
710) -> Result<Option<Vec<u8>>> {
711    let mut out = vec![kind.tag()];
712    put_u32(&mut out, u32::try_from(values.len()).map_err(|_| too_long(values.len()))?);
713    match kind {
714        Kind::Constant => {
715            let Some(first) = values.first() else {
716                return Ok(None);
717            };
718            if values.iter().any(|value| value != first) {
719                return Ok(None);
720            }
721            put_u32(&mut out, u32::try_from(first.len()).map_err(|_| too_long(first.len()))?);
722            out.extend_from_slice(first);
723        }
724        Kind::Plain => {
725            out.extend_from_slice(&encode_lengths(values, chooser)?);
726            for value in values {
727                out.extend_from_slice(value);
728            }
729        }
730        Kind::Fsst => {
731            let trained;
732            let table = match chooser.symbols(depth) {
733                Some(table) => table,
734                None => {
735                    trained = SymbolTable::train(&sample_of(values));
736                    &trained
737                }
738            };
739            if table.is_empty() {
740                return Ok(None);
741            }
742            let mut compressed = Vec::new();
743            let mut lengths = Vec::with_capacity(values.len());
744            for value in values {
745                let before = compressed.len();
746                table.compress(value, &mut compressed);
747                lengths.push((compressed.len() - before) as i64);
748            }
749            table.serialize(&mut out);
750            out.extend_from_slice(&integer::encode_with(&lengths, chooser)?);
751            out.extend_from_slice(&compressed);
752        }
753        Kind::Dict => {
754            let (entries, codes) = dictionary_of(values);
755            if entries.is_empty() {
756                return Ok(None);
757            }
758            out.extend_from_slice(&encode_at(&entries, depth + 1, chooser)?);
759            out.extend_from_slice(&integer::encode_with(&codes, chooser)?);
760        }
761        Kind::Front => {
762            let (prefixes, suffixes) = front_code(values);
763            out.extend_from_slice(&integer::encode_with(&prefixes, chooser)?);
764            out.extend_from_slice(&encode_at(&suffixes, depth + 1, chooser)?);
765        }
766        Kind::Lz => {
767            let mut joined = Vec::with_capacity(total_len(values));
768            let mut sizes = Vec::with_capacity(values.len());
769            for value in values {
770                joined.extend_from_slice(value);
771                sizes.push(value.len() as i64);
772            }
773            let tokens = lz::tokens_of(&joined);
774            out.extend_from_slice(&integer::encode_with(&sizes, chooser)?);
775            out.extend_from_slice(&integer::encode_with(&tokens.lengths, chooser)?);
776            out.extend_from_slice(&integer::encode_with(&tokens.offsets, chooser)?);
777            out.extend_from_slice(&encode_at(&tokens.literals, depth + 1, chooser)?);
778        }
779    }
780    Ok(Some(out))
781}
782
783fn decode_chunk(reader: &mut Reader<'_>) -> Result<Flat> {
784    let kind = Kind::from_tag(reader.u8()?)?;
785    let count = reader.u32()? as usize;
786    match kind {
787        Kind::Constant => {
788            let len = reader.u32()? as usize;
789            let value = reader.bytes(len)?;
790            let mut flat = Flat::with_capacity(count, len.saturating_mul(count));
791            for _ in 0..count {
792                flat.push(value);
793            }
794            Ok(flat)
795        }
796        Kind::Plain => {
797            let lengths = decode_lengths(reader, count)?;
798            // One copy of the whole payload rather than one a value, which the file already laid
799            // out end to end and which is the layout wanted back.
800            let total = sum_of(&lengths)?;
801            let payload = reader.bytes(total)?;
802            let mut flat = Flat::with_capacity(count, total);
803            flat.bytes.extend_from_slice(payload);
804            let mut at = 0;
805            for length in lengths {
806                at += length;
807                flat.ends.push(at);
808            }
809            Ok(flat)
810        }
811        Kind::Fsst => {
812            let runs = read_compressed(reader, count)?;
813            let mut flat = Flat::with_capacity(count, 0);
814            runs.all_into(&mut flat)?;
815            Ok(flat)
816        }
817        Kind::Dict => {
818            let dictionary = decode_chunk(reader)?;
819            let codes = decode_integers(reader)?;
820            if codes.len() != count {
821                return Err(Error::internal(format!(
822                    "a dictionary chunk says it holds {count} values and has {} codes",
823                    codes.len()
824                )));
825            }
826            let mut flat = Flat::with_capacity(count, dictionary.bytes.len());
827            for code in codes {
828                let entry =
829                    usize::try_from(code).ok().and_then(|index| dictionary.get(index)).ok_or_else(
830                        || Error::internal(format!("code {code} is not in the dictionary")),
831                    )?;
832                flat.push(entry);
833            }
834            Ok(flat)
835        }
836        Kind::Front => {
837            let prefixes = decode_integers(reader)?;
838            let suffixes = decode_chunk(reader)?;
839            if prefixes.len() != count || suffixes.len() != count {
840                return Err(Error::internal(format!(
841                    "a front coded chunk says it holds {count} values and has {} prefixes and {} suffixes",
842                    prefixes.len(),
843                    suffixes.len()
844                )));
845            }
846            // The shared prefix is copied out of the buffer being written into, so a value never
847            // has to exist anywhere but where it belongs. The buffer is sized for the prefixes as
848            // well as the suffixes, since sized for the suffixes alone a sorted block of URLs,
849            // whose values share most of their bytes, doubled its way up and copied itself each
850            // time. Walking the lengths first also checks every prefix against the value before
851            // it, so a corrupt one is refused before anything is allocated for it.
852            let mut room = 0usize;
853            let mut previous = 0usize;
854            for (index, prefix) in prefixes.iter().enumerate() {
855                let shared = usize::try_from(*prefix)
856                    .map_err(|_| Error::internal("a negative shared prefix length"))?;
857                if shared > previous {
858                    return Err(Error::internal(format!(
859                        "a value shares {shared} bytes with a value {previous} bytes long"
860                    )));
861                }
862                previous = shared + suffixes.get(index).map_or(0, <[u8]>::len);
863                room = room
864                    .checked_add(previous)
865                    .ok_or_else(|| Error::internal("a string chunk longer than memory"))?;
866            }
867            let mut flat = Flat::with_capacity(count, room);
868            for (index, &prefix) in prefixes.iter().enumerate() {
869                let shared = prefix as usize;
870                let from = if index == 0 { 0 } else { flat.start(index - 1) };
871                flat.bytes.extend_from_within(from..from + shared);
872                flat.bytes.extend_from_slice(suffixes.get(index).expect("in range"));
873                flat.ends.push(flat.bytes.len());
874            }
875            Ok(flat)
876        }
877        Kind::Lz => {
878            let sizes = decode_integers(reader)?;
879            let lengths = decode_integers(reader)?;
880            let offsets = decode_integers(reader)?;
881            if sizes.len() != count {
882                return Err(Error::internal(format!(
883                    "a matched chunk says it holds {count} values and has {} lengths",
884                    sizes.len()
885                )));
886            }
887            let mut total = 0usize;
888            let mut widths = Vec::with_capacity(count);
889            for size in sizes {
890                let width = usize::try_from(size)
891                    .map_err(|_| Error::internal("a negative string length"))?;
892                total = total
893                    .checked_add(width)
894                    .ok_or_else(|| Error::internal("a string chunk longer than memory"))?;
895                widths.push(width);
896            }
897            // The copies point back into the bytes already replayed, which is the buffer the values
898            // are going into, so the replay is the decode and there is nothing to cut up after it.
899            // The room past the end is what the replay's wide stores want, and leaving it out had
900            // the replay grow the buffer, which copied every block once more into fresh pages.
901            let mut flat = Flat::with_capacity(count, total.saturating_add(REPLAY_SLACK));
902            replay_literals(reader, &lengths, &offsets, total, &mut flat.bytes)?;
903            if flat.bytes.len() != total {
904                return Err(Error::internal(format!(
905                    "a matched chunk rebuilt {} bytes where its lengths add up to {total}",
906                    flat.bytes.len()
907                )));
908            }
909            let mut at = 0;
910            for width in widths {
911                at += width;
912                flat.ends.push(at);
913            }
914            Ok(flat)
915        }
916    }
917}
918
919/// A compressed chunk's symbol table and its runs, left where the file put them.
920///
921/// Reading a compressed chunk into this rather than straight into a buffer is what lets a run be
922/// decompressed where the run belongs. The payload is one slice, the run boundaries come from the
923/// length array, and so asking for a run is a decompress of a subslice and nothing else.
924struct Compressed<'a> {
925    /// The table the runs were compressed against.
926    table: SymbolTable,
927    /// How many compressed bytes each run holds, in order.
928    lengths: Vec<usize>,
929    /// Every run's compressed bytes, end to end.
930    payload: &'a [u8],
931}
932
933impl Compressed<'_> {
934    /// Decompresses run `index` onto the end of `out`, with `at` saying where the run starts.
935    ///
936    /// The caller carries the offset because the runs are asked for in order, and adding a length
937    /// per run is cheaper than the prefix sum the alternative wants.
938    ///
939    /// # Errors
940    ///
941    /// If there is no such run, if it runs off the end of the payload, or if it does not decompress.
942    fn run_into(&self, index: usize, at: &mut usize, out: &mut Vec<u8>) -> Result<()> {
943        self.table.decompress(self.run(index, at)?, out)
944    }
945
946    /// Decompresses every run in order onto the end of `flat`.
947    ///
948    /// Each symbol is one eight byte store into room made ahead of it, which is what
949    /// [`SymbolTable::decompress_at`] is for. A run of `n` codes writes at most `n` symbols of at
950    /// most [`MAX_SYMBOL_LEN`] bytes, the last store included, so that much room past where the run
951    /// starts is all it needs. The room is made by doubling, so the zeroes written to make it add up
952    /// to at most twice what the chunk decompresses to. Growing a vector a symbol at a time and
953    /// cutting it back was a fifth of TPC-H q13, all of it the order comment.
954    ///
955    /// # Errors
956    ///
957    /// If a run is past the end of the chunk or does not decompress.
958    fn all_into(&self, flat: &mut Flat) -> Result<()> {
959        let out = &mut flat.bytes;
960        let mut at = out.len();
961        let mut payload = self.payload;
962        for &run in &self.lengths {
963            let Some((codes, rest)) = payload.split_at_checked(run) else {
964                return Err(Error::internal("a compressed run is past the end of its chunk"));
965            };
966            payload = rest;
967            let need = run
968                .checked_mul(MAX_SYMBOL_LEN)
969                .and_then(|room| room.checked_add(at))
970                .ok_or_else(|| Error::internal("a compressed chunk longer than memory"))?;
971            if out.len() < need {
972                out.resize(need.max(out.len() * 2), 0);
973            }
974            at = self.table.decompress_at(codes, out, at)?;
975            flat.ends.push(at);
976        }
977        out.truncate(at);
978        Ok(())
979    }
980
981    /// The compressed bytes of run `index`, with `at` saying where the run starts and left where
982    /// the next one does.
983    fn run(&self, index: usize, at: &mut usize) -> Result<&[u8]> {
984        let length = *self
985            .lengths
986            .get(index)
987            .ok_or_else(|| Error::internal(format!("run {index} is not in the chunk")))?;
988        let end = at
989            .checked_add(length)
990            .ok_or_else(|| Error::internal("a compressed chunk longer than memory"))?;
991        let run = self
992            .payload
993            .get(*at..end)
994            .ok_or_else(|| Error::internal("a compressed run is past the end of its chunk"))?;
995        *at = end;
996        Ok(run)
997    }
998}
999
1000/// Reads a compressed chunk's table, run lengths and payload without decompressing any of it.
1001///
1002/// The tag and the count have already been read.
1003///
1004/// # Errors
1005///
1006/// If the table does not deserialize, if the length array is not `count` long, or if the lengths
1007/// add up to more than the chunk has left.
1008fn read_compressed<'a>(reader: &mut Reader<'a>, count: usize) -> Result<Compressed<'a>> {
1009    let (table, used) = SymbolTable::deserialize(reader.rest())?;
1010    reader.skip(used)?;
1011    let lengths = decode_lengths(reader, count)?;
1012    // The compressed total is what the payload holds and it is also the only sane guess at the
1013    // decompressed one, so it is checked before it is believed.
1014    let compressed_len = sum_of(&lengths)?;
1015    if compressed_len > reader.remaining() {
1016        return Err(Error::internal(format!(
1017            "a compressed chunk says it holds {compressed_len} bytes and has {}",
1018            reader.remaining()
1019        )));
1020    }
1021    let payload = reader.bytes(compressed_len)?;
1022    Ok(Compressed { table, lengths, payload })
1023}
1024
1025/// Replays a matched chunk's tokens, reading the literal runs out of the nested chunk holding them.
1026///
1027/// The nested chunk is decoded into a buffer and copied out of, the way anything nested is, unless
1028/// it is compressed. On the ClickBench `URL` column it always is, and there a block of a thousand
1029/// values holds about eight thousand seven hundred literal runs, so that buffer is the whole
1030/// block's bytes and copying the runs out of it writes every one of them a second time.
1031/// Decompressing a run straight to where it belongs skips the buffer, the length array that would
1032/// cut it up, and that second pass over the bytes.
1033///
1034/// # Errors
1035///
1036/// Whatever reading the literals or replaying the tokens reports.
1037fn replay_literals(
1038    reader: &mut Reader<'_>,
1039    lengths: &[i64],
1040    offsets: &[i64],
1041    total: usize,
1042    out: &mut Vec<u8>,
1043) -> Result<()> {
1044    if reader.rest().first() == Some(&Kind::Fsst.tag()) {
1045        reader.u8()?;
1046        let runs = reader.u32()? as usize;
1047        let compressed = read_compressed(reader, runs)?;
1048        return replay_in_place(&compressed, lengths, offsets, total, out);
1049    }
1050    let literals = decode_chunk(reader)?;
1051    lz::rebuild_into(&literals, lengths, offsets, out)
1052}
1053
1054/// Room past the end of a replay, for the stores that write whole words past where a value ends.
1055///
1056/// A symbol is stored as eight bytes and a copy as sixteen at a time, and each is followed by a
1057/// step of the cursor to where the bytes it meant end. What lands past that is written over by
1058/// whatever comes next, or cut off at the end.
1059const REPLAY_SLACK: usize = 16;
1060
1061/// [`lz::replay`] over compressed literal runs, into a buffer made the length of the output first.
1062///
1063/// The output length is known before a byte is decoded, because the chunk stores the length of
1064/// every value. So the buffer is sized once and written through a cursor, and a symbol or a copy is
1065/// a fixed width store rather than a push that checks capacity and moves a length. The copies were
1066/// the reason: on ClickBench `URL` a block of a thousand values replays about eight thousand seven
1067/// hundred of them, most of them a few tens of bytes, and each one was a call into `memmove`.
1068///
1069/// # Errors
1070///
1071/// As [`lz::replay`], and if the tokens build more than `total` bytes.
1072fn replay_in_place(
1073    compressed: &Compressed<'_>,
1074    lengths: &[i64],
1075    offsets: &[i64],
1076    total: usize,
1077    out: &mut Vec<u8>,
1078) -> Result<()> {
1079    let runs = compressed.lengths.len();
1080    if runs != lengths.len() || lengths.len() != offsets.len() {
1081        return Err(Error::internal(format!(
1082            "a matched chunk has {runs} literal runs, {} lengths and {} offsets",
1083            lengths.len(),
1084            offsets.len()
1085        )));
1086    }
1087    let base = out.len();
1088    let room = total
1089        .checked_add(REPLAY_SLACK)
1090        .ok_or_else(|| Error::internal("a string chunk longer than memory"))?;
1091    out.resize(base + room, 0);
1092    let mut payload = compressed.payload;
1093    let mut at = base;
1094    for ((&run, &length), &offset) in compressed.lengths.iter().zip(lengths).zip(offsets) {
1095        let Some((codes, rest)) = payload.split_at_checked(run) else {
1096            return Err(Error::internal("a compressed run is past the end of its chunk"));
1097        };
1098        payload = rest;
1099        at = compressed.table.decompress_at(codes, out, at)?;
1100        let length =
1101            usize::try_from(length).map_err(|_| Error::internal("a negative copy length"))?;
1102        if length == 0 {
1103            continue;
1104        }
1105        let offset =
1106            usize::try_from(offset).map_err(|_| Error::internal("a negative copy offset"))?;
1107        at = copy_back(out, base, at, offset, length)?;
1108    }
1109    if at > base + total {
1110        return Err(Error::internal(format!(
1111            "a matched chunk rebuilt {} bytes where its lengths add up to {total}",
1112            at - base
1113        )));
1114    }
1115    out.truncate(at);
1116    Ok(())
1117}
1118
1119/// Copies `length` bytes from `offset` back to `at`, handing back where the copy ends.
1120///
1121/// Sixteen bytes at a time where the copy starts at least sixteen bytes back, since then no store
1122/// reads a byte it has not been given yet, and eight at a time where it starts eight back. Nearer
1123/// than that the copy is repeating a short run and goes a byte at a time, the way it always did.
1124/// The whole width stores need room past the end of the copy, and a copy near the end of the buffer
1125/// that does not have it goes a byte at a time too.
1126fn copy_back(
1127    out: &mut [u8],
1128    base: usize,
1129    at: usize,
1130    offset: usize,
1131    length: usize,
1132) -> Result<usize> {
1133    if offset == 0 || offset > at - base {
1134        return Err(Error::internal(format!(
1135            "a copy reaches {offset} bytes back into {} bytes of output",
1136            at - base
1137        )));
1138    }
1139    let end = at
1140        .checked_add(length)
1141        .filter(|&end| end <= out.len())
1142        .ok_or_else(|| Error::internal("a matched chunk rebuilds more than its lengths say"))?;
1143    let from = at - offset;
1144    let wide = end + REPLAY_SLACK <= out.len();
1145    if wide && offset >= 16 {
1146        let mut step = 0;
1147        while step < length {
1148            out.copy_within(from + step..from + step + 16, at + step);
1149            step += 16;
1150        }
1151    } else if wide && offset >= 8 {
1152        let mut step = 0;
1153        while step < length {
1154            out.copy_within(from + step..from + step + 8, at + step);
1155            step += 8;
1156        }
1157    } else {
1158        for step in 0..length {
1159            out[at + step] = out[from + step];
1160        }
1161    }
1162    Ok(end)
1163}
1164
1165fn describe_chunk(reader: &mut Reader<'_>) -> Result<String> {
1166    let kind = Kind::from_tag(reader.u8()?)?;
1167    let count = reader.u32()? as usize;
1168    Ok(match kind {
1169        Kind::Constant => {
1170            let len = reader.u32()? as usize;
1171            reader.bytes(len)?;
1172            "CONSTANT".to_string()
1173        }
1174        Kind::Plain => {
1175            let (shape, lengths) = describe_lengths(reader, count)?;
1176            reader.skip(lengths.iter().sum())?;
1177            format!("PLAIN({shape})")
1178        }
1179        Kind::Fsst => {
1180            let (table, used) = SymbolTable::deserialize(reader.rest())?;
1181            reader.skip(used)?;
1182            let (shape, lengths) = describe_lengths(reader, count)?;
1183            reader.skip(lengths.iter().sum())?;
1184            format!("FSST[{}]({shape})", table.len())
1185        }
1186        Kind::Dict => {
1187            let entries = describe_chunk(reader)?;
1188            let codes = describe_integers(reader)?;
1189            format!("DICT({entries}, {codes})")
1190        }
1191        Kind::Front => {
1192            let prefixes = describe_integers(reader)?;
1193            let suffixes = describe_chunk(reader)?;
1194            format!("FRONT({prefixes}, {suffixes})")
1195        }
1196        Kind::Lz => {
1197            let sizes = describe_integers(reader)?;
1198            let lengths = describe_integers(reader)?;
1199            let offsets = describe_integers(reader)?;
1200            let literals = describe_chunk(reader)?;
1201            format!("LZ({sizes}, {lengths}, {offsets}, {literals})")
1202        }
1203    })
1204}
1205
1206/// The shape of the length array and the lengths themselves, because a describe has to walk past
1207/// the payload to leave the reader where the next chunk starts and the payload size is the sum of
1208/// the lengths.
1209fn describe_lengths(reader: &mut Reader<'_>, count: usize) -> Result<(String, Vec<usize>)> {
1210    let (shape, _) = integer::describe_prefix(reader.rest())?;
1211    let lengths = decode_lengths(reader, count)?;
1212    Ok((shape, lengths))
1213}
1214
1215fn encode_lengths(values: &[&[u8]], chooser: &dyn Chooser) -> Result<Vec<u8>> {
1216    let lengths: Vec<i64> = values.iter().map(|value| value.len() as i64).collect();
1217    integer::encode_with(&lengths, chooser)
1218}
1219
1220fn decode_lengths(reader: &mut Reader<'_>, count: usize) -> Result<Vec<usize>> {
1221    let lengths = decode_integers(reader)?;
1222    if lengths.len() != count {
1223        return Err(Error::internal(format!(
1224            "a string chunk says it holds {count} values and has {} lengths",
1225            lengths.len()
1226        )));
1227    }
1228    lengths
1229        .into_iter()
1230        .map(|length| {
1231            usize::try_from(length).map_err(|_| Error::internal("a negative string length"))
1232        })
1233        .collect()
1234}
1235
1236/// How long the values add up to, refusing a length array that adds up to more than memory.
1237///
1238/// A truncated chunk used to be caught by the read of the value that ran off the end. Reading the
1239/// payload in one go means the total has to be trusted before the read rather than after it, and a
1240/// corrupt length array is the only thing that could overflow it.
1241fn sum_of(lengths: &[usize]) -> Result<usize> {
1242    lengths
1243        .iter()
1244        .try_fold(0usize, |total, length| total.checked_add(*length))
1245        .ok_or_else(|| Error::internal("a string chunk longer than memory"))
1246}
1247
1248/// Reads one nested integer chunk. The integer decoder wants a slice of exactly its own chunk and
1249/// the reader does not know how long that is, so it decodes from the rest of the buffer and is told
1250/// afterwards how much it used.
1251fn decode_integers(reader: &mut Reader<'_>) -> Result<Vec<i64>> {
1252    let (values, used) = integer::decode_prefix(reader.rest())?;
1253    reader.skip(used)?;
1254    Ok(values)
1255}
1256
1257fn describe_integers(reader: &mut Reader<'_>) -> Result<String> {
1258    let (text, used) = integer::describe_prefix(reader.rest())?;
1259    reader.skip(used)?;
1260    Ok(text)
1261}
1262
1263/// A sample of the column spread across the whole of it, taken at random skips rather than at a
1264/// fixed stride.
1265///
1266/// Section 6.3 makes the point about choosing an encoding from a sample and it applies at least as
1267/// much to training a symbol table. Column data is frequently sorted or clustered, so the first
1268/// 64 KB of a URL column is the hosts that sort first and a table trained on it escapes most of the
1269/// rest of the column.
1270///
1271/// The skips are random rather than fixed because a fixed stride aliases. Column data is also
1272/// frequently periodic, and a stride that shares a factor with the period samples one phase of it
1273/// and never sees the others. That is not a hypothetical: the first version of this took every
1274/// `n`th value, and on a test column whose values cycle with a period that the stride happened to
1275/// divide, the table it trained was 3.4 times worse than one trained on the whole column, because
1276/// it learned eight byte symbols that only line up with the phase it saw and had no shorter symbols
1277/// left to fall back on.
1278///
1279/// The generator is a fixed seed xorshift, so the sample is a function of the column and encoding
1280/// the same values twice produces the same bytes.
1281pub(crate) fn sample_of<'a>(values: &[&'a [u8]]) -> Vec<&'a [u8]> {
1282    sample_bytes_of(values, SAMPLE_BYTES)
1283}
1284
1285/// [`sample_of`] with the byte budget spelled out, for a caller training one table over several
1286/// columns that has to split the budget between them.
1287pub(crate) fn sample_bytes_of<'a>(values: &[&'a [u8]], budget: usize) -> Vec<&'a [u8]> {
1288    let budget = budget.max(1);
1289    let total: usize = values.iter().map(|value| value.len()).sum();
1290    if total <= budget {
1291        return values.to_vec();
1292    }
1293    let stride = total.div_ceil(budget).max(1);
1294    let span = (stride * 2 - 1).max(1) as u64;
1295    let mut state = 0x2545_f491_4f6c_dd1du64;
1296    let mut sample = Vec::with_capacity(values.len() / stride + 1);
1297    let mut at = 0usize;
1298    while at < values.len() {
1299        sample.push(values[at]);
1300        state ^= state << 13;
1301        state ^= state >> 7;
1302        state ^= state << 17;
1303        at += 1 + (state % span) as usize;
1304    }
1305    sample
1306}
1307
1308/// The distinct values in sorted order and the code of every value, in one pass over one sort.
1309///
1310/// The dictionary is sorted for the same reason the integer one is: an ordered dictionary turns a
1311/// range predicate into a code range rather than a code set, and front coding over the entries needs
1312/// them sorted anyway.
1313///
1314/// It sorts a permutation of indices rather than the values, which is the whole point. Sorting the
1315/// values means copying every one of them onto the heap first, and the codes then have to be found
1316/// by searching the dictionary back for each value, which is a binary search of string comparisons
1317/// per row. Walking the permutation gives the codes away for free, because the position a value
1318/// sorted to is the position its code was assigned at.
1319fn dictionary_of<'a>(values: &[&'a [u8]]) -> (Vec<&'a [u8]>, Vec<i64>) {
1320    let mut order: Vec<u32> = (0..values.len() as u32).collect();
1321    order.sort_unstable_by(|left, right| values[*left as usize].cmp(values[*right as usize]));
1322    let mut entries: Vec<&'a [u8]> = Vec::new();
1323    let mut codes = vec![0i64; values.len()];
1324    for &index in &order {
1325        let value = values[index as usize];
1326        if entries.last() != Some(&value) {
1327            entries.push(value);
1328        }
1329        codes[index as usize] = (entries.len() - 1) as i64;
1330    }
1331    (entries, codes)
1332}
1333
1334/// Whether any value appears twice, which is the only thing the candidate list wants to know.
1335///
1336/// This used to build the whole sorted dictionary and compare its length against the input, which
1337/// is a copy of the chunk and a sort of it paid on every chunk at every level whether the dictionary
1338/// was ever encoded or not. It is a linear probe over hashes instead: expected O(n), no allocation
1339/// per value, and it stops at the first duplicate it finds, which on a column with any repetition at
1340/// all is immediately.
1341///
1342/// A hash collision is resolved by comparing the bytes, so the answer is exact rather than probable.
1343fn has_duplicates(values: &[&[u8]]) -> bool {
1344    let Some(slots) = values.len().checked_mul(2).map(usize::next_power_of_two) else {
1345        return false;
1346    };
1347    let mask = slots - 1;
1348    let mut table = vec![u32::MAX; slots];
1349    for (index, value) in values.iter().enumerate() {
1350        let mut at = hash_of(value) as usize & mask;
1351        loop {
1352            let held = table[at];
1353            if held == u32::MAX {
1354                table[at] = index as u32;
1355                break;
1356            }
1357            if values[held as usize] == *value {
1358                return true;
1359            }
1360            at = (at + 1) & mask;
1361        }
1362    }
1363    false
1364}
1365
1366/// FNV-1a over the bytes, eight at a time.
1367///
1368/// Good enough for a table that verifies every hit, and it is not part of the format, so nothing
1369/// depends on which hash this is. Eight bytes at a time because a URL column is long values and a
1370/// byte at a time over a hundred bytes of every one of 122,880 rows is the loop this is here to
1371/// avoid.
1372fn hash_of(value: &[u8]) -> u64 {
1373    let mut hash = 0xcbf2_9ce4_8422_2325_u64;
1374    let mut chunks = value.chunks_exact(8);
1375    for chunk in &mut chunks {
1376        let word = u64::from_le_bytes(chunk.try_into().expect("chunks_exact(8) gives eight bytes"));
1377        hash = (hash ^ word).wrapping_mul(0x1_0000_01b3);
1378    }
1379    for byte in chunks.remainder() {
1380        hash = (hash ^ u64::from(*byte)).wrapping_mul(0x1_0000_01b3);
1381    }
1382    (hash ^ (value.len() as u64)).wrapping_mul(0x1_0000_01b3)
1383}
1384
1385fn too_long(len: usize) -> Error {
1386    Error::internal(format!("a string chunk of {len} is longer than the format allows"))
1387}
1388
1389fn put_u32(out: &mut Vec<u8>, value: u32) {
1390    out.extend_from_slice(&value.to_le_bytes());
1391}
1392
1393#[cfg(test)]
1394mod tests {
1395    use super::*;
1396
1397    fn urls(count: usize) -> Vec<Vec<u8>> {
1398        let hosts = ["www.example.com", "shop.example.com", "news.other.example.org"];
1399        let paths = ["/index.html", "/catalog/item", "/search", "/user/profile/settings"];
1400        (0..count)
1401            .map(|index| {
1402                let host = hosts[index % hosts.len()];
1403                let path = paths[(index / 3) % paths.len()];
1404                format!("http://{host}{path}?session={}&ref=google", index * 7).into_bytes()
1405            })
1406            .collect()
1407    }
1408
1409    /// Only the values asked for come back, in order, from a compressed chunk that steps over the
1410    /// rest and from every other shape, which is decoded whole and picked from.
1411    #[test]
1412    fn the_values_at_some_positions_are_the_ones_a_whole_decode_has_there() {
1413        let values = urls(1000);
1414        let refs: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
1415        let positions = [0_u32, 3, 4, 500, 998, 999];
1416        let wanted: Vec<Vec<u8>> =
1417            positions.iter().map(|&at| values[at as usize].clone()).collect();
1418        for kind in offered(&refs) {
1419            let Some(encoded) = encode_only(kind, &refs).expect("encoded") else { continue };
1420            let flat = decode_flat_at(&encoded, &positions).expect("decoded");
1421            assert_eq!(flat.into_values(), wanted, "{kind:?}");
1422            let none = decode_flat_at(&encoded, &[]).expect("decoded");
1423            assert!(none.is_empty(), "{kind:?}");
1424            assert!(decode_flat_at(&encoded, &[4, 3]).is_err(), "{kind:?}");
1425            assert!(decode_flat_at(&encoded, &[1000]).is_err(), "{kind:?}");
1426        }
1427        let fsst = encode_only(Kind::Fsst, &refs).expect("encoded").expect("compressible");
1428        assert_eq!(decode_flat_at(&fsst, &positions).expect("decoded").into_values(), wanted);
1429    }
1430
1431    fn front_lz() -> Settled {
1432        Settled::new(vec![Kind::Front, Kind::Lz], vec![integer::Kind::Packed])
1433    }
1434
1435    /// The point of the table: every block of a column compresses against one table trained once,
1436    /// and what it writes still reads back as the values, including a block the table was not
1437    /// trained on.
1438    #[test]
1439    fn a_block_compressed_against_the_column_table_reads_back() {
1440        let values = urls(4096);
1441        let refs: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
1442        let blocks: Vec<Vec<&[u8]>> = refs.chunks(1024).take(2).map(<[&[u8]]>::to_vec).collect();
1443        let shape = with_symbols(front_lz(), &blocks);
1444        assert!(shape.symbols(2).is_some(), "FRONT then LZ leaves FSST the third level");
1445        assert!(shape.symbols(1).is_none(), "and only that one");
1446        for block in refs.chunks(1024) {
1447            let encoded = encode_with(block, &shape).expect("encoded");
1448            assert_eq!(decode(&encoded).expect("decoded"), block.to_vec());
1449        }
1450    }
1451
1452    /// A shape that settles on `PLAIN` never tries FSST, so there is nothing to train.
1453    #[test]
1454    fn a_shape_ending_in_plain_gets_no_table() {
1455        let values = urls(1024);
1456        let refs: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
1457        let plain = Settled::new(vec![Kind::Lz, Kind::Plain], vec![integer::Kind::Packed]);
1458        let shape = with_symbols(plain, std::slice::from_ref(&refs));
1459        assert!((0..=MAX_DEPTH).all(|depth| shape.symbols(depth).is_none()));
1460        let fsst = Settled::new(vec![Kind::Fsst], vec![integer::Kind::Packed]);
1461        assert!(with_symbols(fsst, &[refs]).symbols(0).is_some());
1462    }
1463
1464    /// The same values with a scrambled identifier stuck on the front of each, for the tests that
1465    /// need neighbouring values to have nothing in common. Shuffling the order is not enough,
1466    /// because two URLs picked at random still agree on a scheme and often on a host.
1467    fn keyed(values: Vec<Vec<u8>>) -> Vec<Vec<u8>> {
1468        values
1469            .into_iter()
1470            .enumerate()
1471            .map(|(index, value)| {
1472                let key = (index as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15) % 1_000_000_007;
1473                let mut out = format!("{key:010}/").into_bytes();
1474                out.extend_from_slice(&value);
1475                out
1476            })
1477            .collect()
1478    }
1479
1480    fn borrow(values: &[Vec<u8>]) -> Vec<&[u8]> {
1481        values.iter().map(Vec::as_slice).collect()
1482    }
1483
1484    fn round_trip(values: &[Vec<u8>]) -> Vec<u8> {
1485        let borrowed = borrow(values);
1486        let bytes = encode(&borrowed).unwrap();
1487        let back = decode(&bytes).unwrap();
1488        assert_eq!(back, values, "{}", describe(&bytes).unwrap());
1489        check_flat(&bytes, values);
1490        bytes
1491    }
1492
1493    /// The flat form holds the same values and lays them out the way a caller with its own offsets
1494    /// expects. Called from [`round_trip`], so every shape any test in here reaches is checked.
1495    fn check_flat(bytes: &[u8], values: &[Vec<u8>]) {
1496        let flat = decode_flat(bytes).unwrap();
1497        let shape = describe(bytes).unwrap();
1498        assert_eq!(flat.len(), values.len(), "{shape}");
1499        assert_eq!(flat.iter().collect::<Vec<_>>(), borrow(values), "{shape}");
1500        assert_eq!(flat.bytes(), values.concat(), "{shape}");
1501        assert_eq!(flat.get(values.len()), None, "{shape}");
1502    }
1503
1504    fn kind_of(bytes: &[u8]) -> Kind {
1505        Kind::from_tag(bytes[0]).unwrap()
1506    }
1507
1508    #[test]
1509    fn every_shape_decodes_flat_to_what_it_decodes_split() {
1510        // round_trip only sees the shape the chooser picked, which on any one column is one of the
1511        // six. This walks all of them, so PLAIN reading its payload in one go and FRONT copying a
1512        // prefix out of the buffer it is filling are both covered on data they apply to.
1513        let columns =
1514            [urls(600), keyed(urls(600)), vec![b"same".to_vec(); 400], vec![Vec::new(); 7]];
1515        for values in &columns {
1516            let borrowed = borrow(values);
1517            for kind in offered(&borrowed) {
1518                let Some(bytes) = encode_only(kind, &borrowed).unwrap() else {
1519                    continue;
1520                };
1521                assert_eq!(decode(&bytes).unwrap(), *values, "{}", kind.name());
1522                let flat = decode_flat(&bytes).unwrap();
1523                assert_eq!(flat.iter().collect::<Vec<_>>(), borrowed, "{}", kind.name());
1524                assert_eq!(flat.bytes(), values.concat(), "{}", kind.name());
1525            }
1526        }
1527    }
1528
1529    #[test]
1530    fn a_front_coded_chunk_that_shares_more_than_it_has_is_an_error() {
1531        // The prefix chain is the one place the flat decoder reads back out of the buffer it is
1532        // filling, so a prefix longer than the value before it is what would hand back somebody
1533        // else's bytes rather than fail. Built by hand because no encoder produces one.
1534        let suffixes: [&[u8]; 2] = [b"abc", b"x"];
1535        let mut bytes = vec![Kind::Front.tag()];
1536        put_u32(&mut bytes, 2);
1537        bytes.extend_from_slice(&integer::encode(&[0, 9]).unwrap());
1538        bytes.extend_from_slice(&encode_only(Kind::Plain, &suffixes).unwrap().unwrap());
1539        let error = decode_flat(&bytes).expect_err("a nine byte prefix of a three byte value");
1540        assert_eq!(error.message(), "a value shares 9 bytes with a value 3 bytes long");
1541        assert_eq!(decode(&bytes).unwrap_err().message(), error.message());
1542    }
1543
1544    #[test]
1545    fn the_dictionary_is_sorted_and_the_codes_point_back_at_the_values() {
1546        // The two things the dictionary path has to get right, and the reason it is one function
1547        // now rather than a sort followed by a binary search per row.
1548        let values = vec![
1549            b"pear".to_vec(),
1550            b"apple".to_vec(),
1551            b"pear".to_vec(),
1552            b"cherry".to_vec(),
1553            b"apple".to_vec(),
1554        ];
1555        let borrowed = borrow(&values);
1556        let (entries, codes) = dictionary_of(&borrowed);
1557        assert_eq!(entries, vec![b"apple".as_slice(), b"cherry".as_slice(), b"pear".as_slice()]);
1558        assert_eq!(codes, vec![2, 0, 2, 1, 0]);
1559        for (code, value) in codes.iter().zip(&borrowed) {
1560            assert_eq!(entries[*code as usize], *value);
1561        }
1562    }
1563
1564    #[test]
1565    fn a_column_with_nothing_repeated_has_no_duplicates_and_one_with_anything_does() {
1566        let distinct: Vec<Vec<u8>> =
1567            (0..5000).map(|index| format!("value-{index}").into_bytes()).collect();
1568        assert!(!has_duplicates(&borrow(&distinct)));
1569
1570        // One repeat at the far end, so a check that gave up early would miss it.
1571        let mut repeated = distinct.clone();
1572        repeated.push(b"value-0".to_vec());
1573        assert!(has_duplicates(&borrow(&repeated)));
1574
1575        assert!(!has_duplicates(&borrow(&Vec::new())));
1576        assert!(!has_duplicates(&borrow(&[b"one".to_vec()])));
1577        assert!(has_duplicates(&borrow(&vec![b"same".to_vec(); 2])));
1578    }
1579
1580    #[test]
1581    fn long_values_that_differ_only_at_the_end_are_not_confused_for_each_other() {
1582        // The hash is eight bytes at a time and the table verifies every hit, so this is the case
1583        // that says the verify is really there rather than the hash being trusted.
1584        let stem = "http://www.example.com/a/very/long/path/that/goes/on?session=";
1585        let values: Vec<Vec<u8>> =
1586            (0..2000).map(|index| format!("{stem}{index}").into_bytes()).collect();
1587        assert!(!has_duplicates(&borrow(&values)));
1588        let (entries, codes) = dictionary_of(&borrow(&values));
1589        assert_eq!(entries.len(), values.len());
1590        assert_eq!(codes.len(), values.len());
1591    }
1592
1593    #[test]
1594    fn what_the_chooser_returns_is_the_smallest_of_what_it_was_offered() {
1595        // `offered` and `encode_only` are what `cargo xtask encode` splits the chooser's seconds
1596        // with, so they have to describe the chooser that actually runs rather than a second copy
1597        // of its rules that drifts. This is the assertion that keeps the two the same thing: walk
1598        // the list, encode each one alone, and the smallest has to be byte for byte what `encode`
1599        // came back with.
1600        for values in [urls(400), keyed(urls(400)), vec![b"same".to_vec(); 50], Vec::new()] {
1601            let borrowed = borrow(&values);
1602            let chosen = encode(&borrowed).unwrap();
1603            let mut smallest: Option<Vec<u8>> = None;
1604            for kind in offered(&borrowed) {
1605                let Some(bytes) = encode_only(kind, &borrowed).unwrap() else {
1606                    continue;
1607                };
1608                if smallest.as_ref().is_none_or(|best| bytes.len() < best.len()) {
1609                    smallest = Some(bytes);
1610                }
1611            }
1612            assert_eq!(smallest.as_deref(), Some(chosen.as_slice()), "{}", values.len());
1613        }
1614    }
1615
1616    fn raw_size(values: &[Vec<u8>]) -> usize {
1617        values.iter().map(Vec::len).sum::<usize>() + values.len() * 4
1618    }
1619
1620    #[test]
1621    fn a_matched_chunk_replays_literals_whether_or_not_they_are_compressed() {
1622        // The literals of a matched chunk are a chunk of their own, and when that chunk is
1623        // compressed the replay decompresses each run straight into the output instead of into a
1624        // buffer it then copies out of. Both columns here are checked value for value by
1625        // round_trip, so what is left is to show that one of them takes the fused path and the
1626        // other takes the one that decodes the literals first, and that the two agree.
1627        let compressed = describe(&round_trip(&keyed(urls(20_000)))).unwrap();
1628        assert!(compressed.starts_with("LZ(") && compressed.contains(", FSST["), "{compressed}");
1629
1630        let buffered = describe(&round_trip(&keyed(urls(300)))).unwrap();
1631        assert!(buffered.starts_with("LZ(") && buffered.contains(", PLAIN("), "{buffered}");
1632    }
1633
1634    #[test]
1635    fn a_copy_back_writes_what_a_byte_at_a_time_copy_writes_at_every_distance() {
1636        // The wide stores read bytes the same copy wrote a step earlier once the copy is longer
1637        // than its distance, so every distance either side of eight and sixteen is checked against
1638        // the plain loop, at lengths that end short of, on and past a whole store.
1639        let seed: Vec<u8> = (0..40u8).map(|byte| byte.wrapping_mul(37).wrapping_add(11)).collect();
1640        for offset in 1..=seed.len() {
1641            for length in 1..=50 {
1642                let mut wanted = seed.clone();
1643                for _ in 0..length {
1644                    wanted.push(wanted[wanted.len() - offset]);
1645                }
1646                let mut out = seed.clone();
1647                out.resize(seed.len() + length + REPLAY_SLACK, 0);
1648                let end = copy_back(&mut out, 0, seed.len(), offset, length).unwrap();
1649                assert_eq!(&out[..end], wanted.as_slice(), "offset {offset} length {length}");
1650            }
1651        }
1652        let mut short = vec![1, 2, 3, 0];
1653        assert!(copy_back(&mut short, 0, 3, 1, 2).is_err(), "past the end of the buffer");
1654        assert!(copy_back(&mut short, 0, 3, 4, 1).is_err(), "further back than the output");
1655    }
1656
1657    #[test]
1658    fn an_empty_chunk_round_trips() {
1659        let bytes = round_trip(&[]);
1660        assert_eq!(kind_of(&bytes), Kind::Plain);
1661    }
1662
1663    #[test]
1664    fn a_constant_column_costs_what_one_value_costs() {
1665        let values = vec![b"https://www.example.com/".to_vec(); 100_000];
1666        let bytes = round_trip(&values);
1667        assert_eq!(kind_of(&bytes), Kind::Constant);
1668        assert_eq!(bytes.len(), 9 + 24);
1669    }
1670
1671    #[test]
1672    fn a_url_column_of_unique_values_is_matched_rather_than_only_compressed() {
1673        // Every value distinct, so a dictionary is the values plus an index and cannot win, and
1674        // every value starts with an identifier of its own, so neighbours share nothing and front
1675        // coding cannot win either. This used to be the case that fell back to FSST, on the
1676        // reasoning that a symbol table was the only thing that could reach repeated vocabulary
1677        // with no structure around it. That reasoning was wrong and #575 is the measurement: the
1678        // vocabulary repeats at a distance, and a match finder reaches distance where a 255 symbol
1679        // table of at most eight bytes each does not.
1680        let values = keyed(urls(20_000));
1681        let bytes = round_trip(&values);
1682        assert_eq!(kind_of(&bytes), Kind::Lz);
1683
1684        // Against the encoding that used to win, on the same values, so the claim is a comparison
1685        // and not just a label.
1686        let borrowed: Vec<&[u8]> = values.iter().map(Vec::as_slice).collect();
1687        let fsst = encode_as(Kind::Fsst, &borrowed, 0, &EXHAUSTIVE).unwrap().unwrap();
1688        assert!(bytes.len() < fsst.len(), "{} against FSST {}", bytes.len(), fsst.len());
1689
1690        // Eleven bytes of every value are the identifier and a separator and nothing compresses
1691        // them, so the ratio here is lower than the one FSST gets on the URLs on their own.
1692        let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1693        assert!(ratio > 4.0, "{ratio:.2}x");
1694    }
1695
1696    #[test]
1697    fn a_sample_of_a_periodic_column_learns_every_phase_of_it() {
1698        // This column is periodic and its period is what a fixed stride would have divided. The
1699        // sample has to see all of it, because a table trained on one phase learns eight byte
1700        // symbols that only line up with that phase and has nothing shorter to fall back on. The
1701        // measured cost of getting this wrong was 3.4 times the compressed size.
1702        let values = urls(20_000);
1703        let borrowed = borrow(&values);
1704        let sample = sample_of(&borrowed);
1705        let mut phases: Vec<&[u8]> = sample
1706            .iter()
1707            .map(|value| {
1708                let query =
1709                    value.iter().position(|byte| *byte == b'?').expect("every value has a query");
1710                &value[..query]
1711            })
1712            .collect();
1713        phases.sort_unstable();
1714        phases.dedup();
1715        // Three hosts and four paths, and the sample has to contain all twelve of the combinations.
1716        assert_eq!(phases.len(), 12);
1717        let whole = SymbolTable::train(&borrowed);
1718        let sampled = SymbolTable::train(&sample);
1719        let mut on_whole = Vec::new();
1720        let mut on_sample = Vec::new();
1721        for value in &borrowed {
1722            whole.compress(value, &mut on_whole);
1723            sampled.compress(value, &mut on_sample);
1724        }
1725        // Training on a twentieth of the column is allowed to cost something. It is not allowed to
1726        // cost a factor.
1727        assert!(
1728            on_sample.len() < on_whole.len() * 5 / 4,
1729            "{} against {}",
1730            on_sample.len(),
1731            on_whole.len()
1732        );
1733    }
1734
1735    #[test]
1736    fn a_repeating_column_becomes_a_dictionary_of_compressed_entries() {
1737        // The DICT_FSST row of the section 6.2 table, which is not an encoding of its own here: it
1738        // is a dictionary whose entries went back through the chooser. What the entries then get
1739        // is whatever wins on them, and since #575 that is the match finder rather than front
1740        // coding with the leftovers FSST compressed. The point of the test is unchanged: nobody
1741        // named the shape and the chooser arrived at it.
1742        //
1743        // The rows pick their value by a hash of the row number. They used to walk the values in
1744        // a fixed stride, which makes the dictionary codes a cycle whose differences take a
1745        // quarter as many values as the codes do, and a real column's codes are not that.
1746        let distinct = urls(500);
1747        let values: Vec<Vec<u8>> = (0..50_000_u64)
1748            .map(|index| {
1749                let hashed = (index.wrapping_mul(0x9E37_79B9_7F4A_7C15) >> 32) as usize;
1750                distinct[hashed % distinct.len()].clone()
1751            })
1752            .collect();
1753        let bytes = round_trip(&values);
1754        assert_eq!(kind_of(&bytes), Kind::Dict);
1755        let shape = describe(&bytes).unwrap();
1756        assert!(shape.starts_with("DICT(LZ("), "{shape}");
1757        let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1758        assert!(ratio > 20.0, "{ratio:.2}x, {shape}");
1759    }
1760
1761    #[test]
1762    fn a_column_of_long_runs_costs_almost_nothing() {
1763        // A dictionary makes the codes an integer chunk, and the integer chunk knows what to do
1764        // with runs, so run length encoding of strings falls out of the recursion.
1765        let distinct = urls(50);
1766        let mut values = Vec::new();
1767        for entry in &distinct {
1768            values.extend(std::iter::repeat_n(entry.clone(), 1000));
1769        }
1770        let bytes = round_trip(&values);
1771        let shape = describe(&bytes).unwrap();
1772        assert!(shape.contains("RLE"), "{shape}");
1773        assert!(bytes.len() < 2000, "{} bytes: {shape}", bytes.len());
1774    }
1775
1776    #[test]
1777    fn incompressible_strings_stay_close_to_their_own_size() {
1778        // The case where nothing works. It has to land on PLAIN or on an FSST that is not much
1779        // worse, rather than on a dictionary of every value in the column.
1780        let mut state = 0x2545_f491_4f6c_dd1du64;
1781        let values: Vec<Vec<u8>> = (0..2000)
1782            .map(|_| {
1783                (0..32)
1784                    .map(|_| {
1785                        state ^= state << 13;
1786                        state ^= state >> 7;
1787                        state ^= state << 17;
1788                        state as u8
1789                    })
1790                    .collect()
1791            })
1792            .collect();
1793        let bytes = round_trip(&values);
1794        assert!(bytes.len() < 2000 * 32 + 3000, "{} bytes", bytes.len());
1795    }
1796
1797    #[test]
1798    fn lengths_are_stored_rather_than_offsets() {
1799        // Every value is 24 bytes, so the lengths are a constant chunk and cost 13 bytes for the
1800        // whole column. Offsets would be 100,000 increasing integers.
1801        let values: Vec<Vec<u8>> =
1802            (0..100_000).map(|index| format!("{index:024}").into_bytes()).collect();
1803        let borrowed = borrow(&values);
1804        let bytes = encode_only(Kind::Plain, &borrowed).unwrap().unwrap();
1805        assert_eq!(bytes.len(), 5 + 13 + 100_000 * 24);
1806    }
1807
1808    #[test]
1809    fn empty_strings_are_values_and_not_nulls() {
1810        let values = vec![Vec::new(), b"a".to_vec(), Vec::new(), b"bb".to_vec()];
1811        round_trip(&values);
1812    }
1813
1814    #[test]
1815    fn a_chunk_with_one_value_round_trips() {
1816        round_trip(&[b"only".to_vec()]);
1817    }
1818
1819    #[test]
1820    fn every_candidate_that_applies_decodes_to_the_input() {
1821        let values = urls(3000);
1822        let borrowed = borrow(&values);
1823        let applicable = candidates(&borrowed, 0);
1824        assert!(applicable.len() >= 2, "{applicable:?}");
1825        for kind in applicable {
1826            let bytes = encode_only(kind, &borrowed).unwrap().unwrap();
1827            assert_eq!(decode(&bytes).unwrap(), values, "{}", kind.name());
1828        }
1829    }
1830
1831    #[test]
1832    fn the_chooser_picks_the_smallest_candidate() {
1833        let values = urls(2000);
1834        let borrowed = borrow(&values);
1835        let chosen = encode(&borrowed).unwrap();
1836        for (_, size) in candidate_sizes(&borrowed).unwrap() {
1837            assert!(chosen.len() <= size);
1838        }
1839    }
1840
1841    #[test]
1842    fn a_truncated_chunk_is_an_error_and_not_a_panic() {
1843        let values = urls(40);
1844        let bytes = encode(&borrow(&values)).unwrap();
1845        for len in 0..bytes.len() {
1846            assert!(decode(&bytes[..len]).is_err(), "{len} bytes decoded");
1847        }
1848    }
1849
1850    #[test]
1851    fn trailing_bytes_are_an_error() {
1852        let mut bytes = encode(&borrow(&urls(10))).unwrap();
1853        bytes.push(0);
1854        let error = decode(&bytes).unwrap_err();
1855        assert!(error.message().contains("left over"), "{error}");
1856    }
1857
1858    #[test]
1859    fn an_unknown_tag_is_an_error() {
1860        let error = decode(&[99, 0, 0, 0, 0]).unwrap_err();
1861        assert!(error.message().contains("unknown string encoding tag"), "{error}");
1862    }
1863
1864    #[test]
1865    fn a_dictionary_code_outside_the_dictionary_is_an_error() {
1866        let mut bytes = vec![Kind::Dict.tag()];
1867        put_u32(&mut bytes, 1);
1868        bytes.extend_from_slice(&encode(&[b"one".as_slice()]).unwrap());
1869        bytes.extend_from_slice(&integer::encode(&[9]).unwrap());
1870        let error = decode(&bytes).unwrap_err();
1871        assert!(error.message().contains("not in the dictionary"), "{error}");
1872    }
1873
1874    #[test]
1875    fn a_sorted_column_of_urls_is_front_coded() {
1876        // The M1 finding, in a test. Sorted URLs share a host and most of a path with the URL next
1877        // to them, FSST cannot reach those bytes because it compresses each value on its own, and
1878        // front coding is the shape that reaches them.
1879        let mut values = urls(20_000);
1880        values.sort();
1881        let bytes = round_trip(&values);
1882        assert_eq!(kind_of(&bytes), Kind::Front);
1883        let shape = describe(&bytes).unwrap();
1884        let mut plain = Vec::new();
1885        let borrowed = borrow(&values);
1886        for (kind, size) in candidate_sizes(&borrowed).unwrap() {
1887            if kind == Kind::Fsst {
1888                plain.push(size);
1889            }
1890        }
1891        let fsst = plain[0];
1892        assert!(bytes.len() * 2 < fsst, "{} against FSST {fsst}: {shape}", bytes.len());
1893    }
1894
1895    #[test]
1896    fn a_column_with_nothing_to_share_is_not_offered_front_coding() {
1897        // The candidate costs an encode of the whole column, so a column whose neighbours have
1898        // nothing in common must not be paying for it.
1899        let mut state = 0x9e37_79b9_7f4a_7c15u64;
1900        let values: Vec<Vec<u8>> = (0..2000)
1901            .map(|_| {
1902                (0..24)
1903                    .map(|_| {
1904                        state ^= state << 13;
1905                        state ^= state >> 7;
1906                        state ^= state << 17;
1907                        (state % 251) as u8
1908                    })
1909                    .collect()
1910            })
1911            .collect();
1912        let borrowed = borrow(&values);
1913        assert!(!candidates(&borrowed, 0).contains(&Kind::Front));
1914    }
1915
1916    #[test]
1917    fn a_prefix_longer_than_the_value_before_it_is_an_error() {
1918        let mut bytes = vec![Kind::Front.tag()];
1919        put_u32(&mut bytes, 2);
1920        bytes.extend_from_slice(&integer::encode(&[0, 9]).unwrap());
1921        bytes.extend_from_slice(&encode(&[b"one".as_slice(), b"two".as_slice()]).unwrap());
1922        let error = decode(&bytes).unwrap_err();
1923        assert!(error.message().contains("shares 9 bytes"), "{error}");
1924    }
1925
1926    #[test]
1927    fn a_negative_prefix_is_an_error() {
1928        let mut bytes = vec![Kind::Front.tag()];
1929        put_u32(&mut bytes, 1);
1930        bytes.extend_from_slice(&integer::encode(&[-1]).unwrap());
1931        bytes.extend_from_slice(&encode(&[b"one".as_slice()]).unwrap());
1932        let error = decode(&bytes).unwrap_err();
1933        assert!(error.message().contains("negative shared prefix"), "{error}");
1934    }
1935
1936    #[test]
1937    fn a_negative_length_is_an_error() {
1938        let mut bytes = vec![Kind::Plain.tag()];
1939        put_u32(&mut bytes, 1);
1940        bytes.extend_from_slice(&integer::encode(&[-1]).unwrap());
1941        let error = decode(&bytes).unwrap_err();
1942        assert!(error.message().contains("negative string length"), "{error}");
1943    }
1944
1945    #[test]
1946    fn the_sample_is_spread_across_the_chunk_and_not_taken_from_the_front() {
1947        // A sorted column whose first 64 KB says nothing about the rest of it. If the sample were
1948        // the front, the table would learn `aaaa` and escape every `zzzz`.
1949        let mut values: Vec<Vec<u8>> = Vec::new();
1950        for index in 0..20_000 {
1951            let head = if index < 10_000 { "aaaaaaaaaaaaaaaa" } else { "zzzzzzzzzzzzzzzz" };
1952            values.push(format!("{head}/{index:08}").into_bytes());
1953        }
1954        let borrowed = borrow(&values);
1955        let sample = sample_of(&borrowed);
1956        let first_half = sample.iter().filter(|value| value.starts_with(b"aaaa")).count();
1957        let second_half = sample.len() - first_half;
1958        assert!(first_half > 0 && second_half > 0, "{first_half} and {second_half}");
1959        let bytes = round_trip(&values);
1960        let ratio = raw_size(&values) as f64 / bytes.len() as f64;
1961        assert!(ratio > 4.0, "{ratio:.2}x");
1962    }
1963}