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