Skip to main content

moraine/store/
index_encoding.rs

1//! Canonical value encoding for equality-index keys.
2//!
3//! An index entry key embeds the indexed column values as bytes. The
4//! contract is canonical bytes for equality: one logical value always
5//! encodes to one byte string, distinct values to distinct byte strings,
6//! per DuckLake column type. Where it is free the encoding is also
7//! order-compatible (byte order matches value order), so a future range
8//! contract is an upgrade, not a rewrite — but only equality is promised.
9
10use std::mem::size_of;
11
12use bytes::Bytes;
13use storekey::{Decode, Encode};
14
15use crate::{
16    error::{Error, Result},
17    store::key::{INDEX_ENTRY_PREFIX_LEN, INDEX_SUBSPACE_TAG, index_kind_tag},
18};
19
20/// Maximum size of a composite index key, summed over its component
21/// canonical encodings; larger values are refused.
22pub(crate) const MAX_INDEX_KEY_BYTES: usize = 1024;
23
24/// Byte width of a fixed-width integer column.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum IntWidth {
27    /// One byte (`TINYINT`, `UTINYINT`).
28    I8,
29    /// Two bytes (`SMALLINT`, `USMALLINT`).
30    I16,
31    /// Four bytes (`INTEGER`, `UINTEGER`).
32    I32,
33    /// Eight bytes (`BIGINT`, `UBIGINT`, and temporal types by their
34    /// underlying representation).
35    I64,
36    /// Sixteen bytes. Reserved: no indexable column type derives this width.
37    I128,
38}
39
40impl IntWidth {
41    /// Width in bytes.
42    const fn bytes(self) -> usize {
43        match self {
44            Self::I8 => 1,
45            Self::I16 => 2,
46            Self::I32 => 4,
47            Self::I64 => 8,
48            Self::I128 => 16,
49        }
50    }
51
52    /// Whether `value` is representable as a signed integer of this width.
53    const fn holds_signed(self, value: i128) -> bool {
54        if matches!(self, Self::I128) {
55            return true;
56        }
57        let bits = self.bytes() * 8;
58        value >= -(1_i128 << (bits - 1)) && value < (1_i128 << (bits - 1))
59    }
60
61    /// Whether `value` is representable as an unsigned integer of this
62    /// width.
63    const fn holds_unsigned(self, value: u128) -> bool {
64        matches!(self, Self::I128) || value < (1_u128 << (self.bytes() * 8))
65    }
66}
67
68/// A single non-null indexed column value, typed by the column's category.
69/// NULL is not a variant here — it is the `None` of a column's optional
70/// value, which encodes to a leading null flag.
71#[derive(Debug, Clone, PartialEq)]
72pub enum IndexKeyValue {
73    /// A signed integer of a fixed width.
74    Int {
75        /// The value, sign-extended into an `i128`.
76        value: i128,
77        /// The column's byte width.
78        width: IntWidth,
79    },
80    /// An unsigned integer of a fixed width.
81    UInt {
82        /// The value, zero-extended into a `u128`.
83        value: u128,
84        /// The column's byte width.
85        width: IntWidth,
86    },
87    /// A single-precision float.
88    F32(f32),
89    /// A double-precision float.
90    F64(f64),
91    /// A boolean.
92    Bool(bool),
93    /// A UTF-8 string.
94    Str(String),
95    /// A raw byte string (blob).
96    Bytes(Vec<u8>),
97}
98
99/// A borrowed indexed scalar used while constructing a persistent key.
100///
101/// Text and binary values stay borrowed from their source array. Fixed-width
102/// values ride by value, so the builder can encode every variant without an
103/// intermediate allocation while remaining independent of Arrow.
104#[derive(Debug, Clone, Copy, PartialEq)]
105pub(crate) enum BorrowedIndexKeyValue<'a> {
106    /// A signed integer of a fixed width.
107    Int { value: i128, width: IntWidth },
108    /// An unsigned integer of a fixed width.
109    UInt { value: u128, width: IntWidth },
110    /// A single-precision float.
111    F32(f32),
112    /// A double-precision float.
113    F64(f64),
114    /// A boolean.
115    Bool(bool),
116    /// A borrowed UTF-8 string.
117    Str(&'a str),
118    /// A borrowed byte string.
119    Bytes(&'a [u8]),
120}
121
122impl<'a> From<&'a IndexKeyValue> for BorrowedIndexKeyValue<'a> {
123    fn from(value: &'a IndexKeyValue) -> Self {
124        match value {
125            IndexKeyValue::Int { value, width } => Self::Int {
126                value: *value,
127                width: *width,
128            },
129            IndexKeyValue::UInt { value, width } => Self::UInt {
130                value: *value,
131                width: *width,
132            },
133            IndexKeyValue::F32(value) => Self::F32(*value),
134            IndexKeyValue::F64(value) => Self::F64(*value),
135            IndexKeyValue::Bool(value) => Self::Bool(*value),
136            IndexKeyValue::Str(value) => Self::Str(value),
137            IndexKeyValue::Bytes(value) => Self::Bytes(value),
138        }
139    }
140}
141
142/// The one quiet-NaN bit pattern every `f32` NaN collapses to.
143const F32_CANONICAL_NAN: u32 = 0x7fc0_0000;
144/// The one quiet-NaN bit pattern every `f64` NaN collapses to.
145const F64_CANONICAL_NAN: u64 = 0x7ff8_0000_0000_0000;
146
147impl IndexKeyValue {
148    /// Canonical bytes for this value.
149    #[cfg(test)]
150    pub(crate) fn encode(&self) -> Vec<u8> {
151        match self {
152            Self::Int { value, width } => {
153                let width = width.bytes();
154                let mut bytes = value.to_be_bytes()[size_of::<i128>() - width..].to_vec();
155                // Flip the sign bit so two's-complement values sort as
156                // unsigned bytes in numeric order.
157                bytes[0] ^= 0x80;
158                bytes
159            }
160            Self::UInt { value, width } => {
161                let width = width.bytes();
162                value.to_be_bytes()[size_of::<u128>() - width..].to_vec()
163            }
164            Self::F32(value) => {
165                let bits = if value.is_nan() {
166                    F32_CANONICAL_NAN
167                } else {
168                    // Adding 0.0 folds -0.0 to +0.0 without touching any
169                    // other value.
170                    (value + 0.0).to_bits()
171                };
172                // Total-order transform: flip the sign bit of a non-negative
173                // value, every bit of a negative one, so the big-endian bytes
174                // sort in numeric order (NaN, collapsed above, sorts greatest).
175                let mask = 0x8000_0000_u32 | 0_u32.wrapping_sub(bits >> 31);
176                (bits ^ mask).to_be_bytes().to_vec()
177            }
178            Self::F64(value) => {
179                let bits = if value.is_nan() {
180                    F64_CANONICAL_NAN
181                } else {
182                    (value + 0.0).to_bits()
183                };
184                let mask = 0x8000_0000_0000_0000_u64 | 0_u64.wrapping_sub(bits >> 63);
185                (bits ^ mask).to_be_bytes().to_vec()
186            }
187            Self::Bool(value) => vec![u8::from(*value)],
188            // The per-component escaping that keeps composite boundaries
189            // unambiguous is applied by the storekey framing of the
190            // enclosing [`CanonicalKey`], not here.
191            Self::Str(value) => value.as_bytes().to_vec(),
192            Self::Bytes(value) => value.clone(),
193        }
194    }
195
196    /// How many canonical bytes [`Self::encode`] would produce, without
197    /// producing them.
198    #[cfg(test)]
199    const fn encoded_len(&self) -> usize {
200        match self {
201            Self::Int { width, .. } | Self::UInt { width, .. } => width.bytes(),
202            Self::F32(_) => size_of::<f32>(),
203            Self::F64(_) => size_of::<f64>(),
204            Self::Bool(_) => 1,
205            Self::Str(value) => value.len(),
206            Self::Bytes(value) => value.len(),
207        }
208    }
209}
210
211/// What [`encode_ordered_values`] would produce for `values`, assuming no
212/// byte needs escaping. A lower bound: escaping can at worst double a value.
213#[cfg(test)]
214pub(crate) fn nominal_key_bytes(values: &[Option<IndexKeyValue>]) -> usize {
215    values
216        .iter()
217        .map(|value| match value {
218            Some(value) => 1 + value.encoded_len() + 1,
219            None => 1,
220        })
221        .sum()
222}
223
224/// Sort direction of an indexed column.
225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226pub enum Direction {
227    /// Ascending — smaller values first.
228    Ascending,
229    /// Descending — larger values first, realized by complementing the
230    /// column's framed bytes.
231    Descending,
232}
233
234/// Where NULLs sort relative to the non-null values of an indexed column,
235/// independent of the column's [`Direction`].
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237pub enum NullOrder {
238    /// NULLs sort before every non-null value.
239    First,
240    /// NULLs sort after every non-null value.
241    Last,
242}
243
244/// One column of an ordered index key: its value (`None` is SQL NULL) and
245/// the ordering the column was declared with.
246#[derive(Debug, Clone, PartialEq)]
247#[cfg(test)]
248pub struct OrderedColumn {
249    /// The column value, or `None` for NULL.
250    pub value: Option<IndexKeyValue>,
251    /// Ascending or descending.
252    pub direction: Direction,
253    /// NULL placement relative to non-null values.
254    pub nulls: NullOrder,
255}
256
257/// The canonical encoding of an index's ordered column values: the
258/// storekey-framed concatenation of the per-column component encodings,
259/// held as one opaque byte string.
260#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
261pub(crate) struct CanonicalKey(Bytes);
262
263// Coded as a storekey byte string rather than by the derive, which routes a
264// `Vec<u8>` through the generic sequence codec. Both emit the same bytes —
265// low bytes escaped behind `0x01`, a `0x00` terminator — but the sequence
266// decoder leaves "the next byte may be escaped" set after consuming that
267// terminator, so a following fixed-width field whose leading byte is `0x01`
268// loses it. A non-unique entry key is exactly that shape: the value, then a
269// raw row id.
270impl<F> Encode<F> for CanonicalKey {
271    fn encode<W: std::io::Write>(
272        &self,
273        w: &mut storekey::Writer<W>,
274    ) -> std::result::Result<(), storekey::EncodeError> {
275        w.write_slice(&self.0)
276    }
277}
278
279impl<F> Decode<F> for CanonicalKey {
280    fn decode<R: std::io::BufRead>(
281        r: &mut storekey::Reader<R>,
282    ) -> std::result::Result<Self, storekey::DecodeError> {
283        Ok(Self(Bytes::from(r.read_vec()?)))
284    }
285}
286
287impl CanonicalKey {
288    /// Bytes retained by this canonical key.
289    pub(crate) fn retained_bytes(&self) -> usize {
290        self.0.len()
291    }
292
293    /// A key with no framed content, for prefix derivation only.
294    pub(crate) const fn empty() -> Self {
295        Self(Bytes::new())
296    }
297
298    /// The framed bytes embedded in an entry key.
299    #[cfg(test)]
300    pub(crate) fn as_bytes(&self) -> &[u8] {
301        &self.0
302    }
303}
304
305/// Builds a canonical composite key directly from borrowed scalar values,
306/// tracking whether any component was NULL.
307pub(crate) struct CanonicalKeyBuilder {
308    bytes: Vec<u8>,
309    raw_bytes: usize,
310    has_null: bool,
311    entry_bytes: Option<usize>,
312    entry_unique: bool,
313}
314
315impl CanonicalKeyBuilder {
316    /// Starts an empty composite key.
317    pub(crate) const fn new() -> Self {
318        Self {
319            bytes: Vec::new(),
320            raw_bytes: 0,
321            has_null: false,
322            entry_bytes: None,
323            entry_unique: false,
324        }
325    }
326
327    /// Reserves for physical framing as columns arrive, including a possible
328    /// row-id suffix.
329    pub(crate) const fn for_index_entry(unique: bool) -> Self {
330        Self {
331            bytes: Vec::new(),
332            raw_bytes: 0,
333            has_null: false,
334            entry_bytes: Some(
335                INDEX_ENTRY_PREFIX_LEN + 1 + if unique { 0 } else { size_of::<u64>() },
336            ),
337            entry_unique: unique,
338        }
339    }
340
341    fn reserve_component(&mut self, canonical_bytes: usize, outer_escapes: usize) {
342        if let Some(entry_bytes) = &mut self.entry_bytes {
343            *entry_bytes += canonical_bytes + outer_escapes;
344            self.bytes
345                .reserve_exact(entry_bytes.saturating_sub(self.bytes.len()));
346        } else {
347            self.bytes.reserve(canonical_bytes);
348        }
349    }
350
351    /// Appends one ordered column. Text and binary values are consumed by
352    /// reference and copied only into the final canonical key.
353    pub(crate) fn append(
354        &mut self,
355        value: Option<BorrowedIndexKeyValue<'_>>,
356        direction: Direction,
357        nulls: NullOrder,
358    ) -> Result<()> {
359        let (null_flag, non_null_flag) = null_flags(nulls);
360        let Some(value) = value else {
361            if self.entry_unique {
362                if let Some(entry_bytes) = &mut self.entry_bytes {
363                    *entry_bytes += size_of::<u64>();
364                }
365                self.entry_unique = false;
366            }
367            self.reserve_component(1, 1);
368            self.bytes.push(null_flag);
369            self.has_null = true;
370            return Ok(());
371        };
372
373        let raw_len = value.encoded_len();
374        let total = self.raw_bytes.saturating_add(raw_len);
375        if total > MAX_INDEX_KEY_BYTES {
376            return Err(Error::Constraint(format!(
377                "index key of {total} bytes exceeds the {MAX_INDEX_KEY_BYTES}-byte limit"
378            )));
379        }
380        value.validate_width()?;
381
382        self.raw_bytes = total;
383        match value {
384            BorrowedIndexKeyValue::Int { value, width } => {
385                let mut raw = value.to_be_bytes();
386                let start = raw.len() - width.bytes();
387                raw[start] ^= 0x80;
388                self.append_framed(&raw[start..], direction, non_null_flag);
389            }
390            BorrowedIndexKeyValue::UInt { value, width } => {
391                let raw = value.to_be_bytes();
392                self.append_framed(&raw[raw.len() - width.bytes()..], direction, non_null_flag);
393            }
394            BorrowedIndexKeyValue::F32(value) => {
395                let bits = if value.is_nan() {
396                    F32_CANONICAL_NAN
397                } else {
398                    (value + 0.0).to_bits()
399                };
400                let mask = 0x8000_0000_u32 | 0_u32.wrapping_sub(bits >> 31);
401                self.append_framed(&(bits ^ mask).to_be_bytes(), direction, non_null_flag);
402            }
403            BorrowedIndexKeyValue::F64(value) => {
404                let bits = if value.is_nan() {
405                    F64_CANONICAL_NAN
406                } else {
407                    (value + 0.0).to_bits()
408                };
409                let mask = 0x8000_0000_0000_0000_u64 | 0_u64.wrapping_sub(bits >> 63);
410                self.append_framed(&(bits ^ mask).to_be_bytes(), direction, non_null_flag);
411            }
412            BorrowedIndexKeyValue::Bool(value) => {
413                self.append_framed(&[u8::from(value)], direction, non_null_flag);
414            }
415            BorrowedIndexKeyValue::Str(value) => {
416                self.append_framed(value.as_bytes(), direction, non_null_flag);
417            }
418            BorrowedIndexKeyValue::Bytes(value) => {
419                self.append_framed(value, direction, non_null_flag);
420            }
421        }
422        Ok(())
423    }
424
425    /// Finishes the key and reports whether any component was NULL.
426    pub(crate) fn finish(self) -> (CanonicalKey, bool) {
427        (CanonicalKey(Bytes::from(self.bytes)), self.has_null)
428    }
429
430    /// Finishes directly as a physical index-entry key, outer-framing the
431    /// canonical bytes in place from back to front.
432    pub(crate) fn finish_index_entry(
433        mut self,
434        index_id: u64,
435        requested_unique: bool,
436        row_id: u64,
437    ) -> (Bytes, bool) {
438        let unique = requested_unique && !self.has_null;
439        let canonical_len = self.bytes.len();
440        let escapes = self.bytes.iter().filter(|byte| **byte <= 1).count();
441        let framed_end = INDEX_ENTRY_PREFIX_LEN
442            .saturating_add(canonical_len)
443            .saturating_add(escapes);
444        let suffix = 1 + if unique { 0 } else { size_of::<u64>() };
445        self.bytes.resize(framed_end.saturating_add(suffix), 0);
446
447        let mut write = framed_end;
448        for read in (0..canonical_len).rev() {
449            let byte = self.bytes[read];
450            write -= 1;
451            self.bytes[write] = byte;
452            if byte <= 1 {
453                write -= 1;
454                self.bytes[write] = 1;
455            }
456        }
457        self.bytes[0] = INDEX_SUBSPACE_TAG;
458        self.bytes[1] = index_kind_tag(unique);
459        self.bytes[2..INDEX_ENTRY_PREFIX_LEN].copy_from_slice(&index_id.to_be_bytes());
460        self.bytes[framed_end] = 0;
461        if !unique {
462            self.bytes[framed_end + 1..].copy_from_slice(&row_id.to_be_bytes());
463        }
464
465        (Bytes::from(self.bytes), unique)
466    }
467
468    fn append_framed(&mut self, raw: &[u8], direction: Direction, flag: u8) {
469        let low_bytes = raw.iter().filter(|byte| **byte <= 1).count();
470        let outer_escapes = 1 + match direction {
471            Direction::Ascending => low_bytes * 2 + 1,
472            Direction::Descending => raw.iter().filter(|byte| **byte >= 254).count(),
473        };
474        self.reserve_component(raw.len() + low_bytes + 2, outer_escapes);
475        self.bytes.push(flag);
476        let start = self.bytes.len();
477        for &byte in raw {
478            if byte <= 1 {
479                self.bytes.push(1);
480            }
481            self.bytes.push(byte);
482        }
483        self.bytes.push(0);
484        if direction == Direction::Descending {
485            for byte in &mut self.bytes[start..] {
486                *byte = !*byte;
487            }
488        }
489    }
490}
491
492impl BorrowedIndexKeyValue<'_> {
493    /// This value with its text or bytes copied out of their source.
494    pub(crate) fn into_owned(self) -> IndexKeyValue {
495        match self {
496            Self::Int { value, width } => IndexKeyValue::Int { value, width },
497            Self::UInt { value, width } => IndexKeyValue::UInt { value, width },
498            Self::F32(value) => IndexKeyValue::F32(value),
499            Self::F64(value) => IndexKeyValue::F64(value),
500            Self::Bool(value) => IndexKeyValue::Bool(value),
501            Self::Str(value) => IndexKeyValue::Str(value.to_owned()),
502            Self::Bytes(value) => IndexKeyValue::Bytes(value.to_vec()),
503        }
504    }
505
506    const fn encoded_len(self) -> usize {
507        match self {
508            Self::Int { width, .. } | Self::UInt { width, .. } => width.bytes(),
509            Self::F32(_) => size_of::<f32>(),
510            Self::F64(_) => size_of::<f64>(),
511            Self::Bool(_) => 1,
512            Self::Str(value) => value.len(),
513            Self::Bytes(value) => value.len(),
514        }
515    }
516
517    fn validate_width(self) -> Result<()> {
518        let fits = match self {
519            Self::Int { value, width } => width.holds_signed(value),
520            Self::UInt { value, width } => width.holds_unsigned(value),
521            _ => true,
522        };
523        if fits {
524            Ok(())
525        } else {
526            Err(Error::Constraint(format!(
527                "index key value {self:?} does not fit its declared integer width"
528            )))
529        }
530    }
531}
532
533/// Canonically encode an equality lookup's column values — the degenerate
534/// all-ascending, non-null case of [`encode_ordered_key`]. A test-only
535/// convenience; production paths call [`encode_ordered_values`] with the
536/// index's declared orders.
537#[cfg(test)]
538pub(crate) fn encode_key(values: &[IndexKeyValue]) -> Result<CanonicalKey> {
539    let columns: Vec<OrderedColumn> = values
540        .iter()
541        .map(|value| OrderedColumn {
542            value: Some(value.clone()),
543            direction: Direction::Ascending,
544            nulls: NullOrder::Last,
545        })
546        .collect();
547    encode_ordered_key(&columns)
548}
549
550/// The `(null_flag, non_null_flag)` pair a column with this NULL placement
551/// prefixes its blob with.
552const fn null_flags(nulls: NullOrder) -> (u8, u8) {
553    match nulls {
554        NullOrder::First => (0x00, 0x01),
555        NullOrder::Last => (0x01, 0x00),
556    }
557}
558
559/// The canonical key naming only a leading column's non-null flag: the
560/// prefix every non-null value of that column shares, and so the boundary
561/// separating them from the column's NULL entries.
562pub(crate) fn non_null_flag_key(nulls: NullOrder) -> CanonicalKey {
563    let (_, non_null_flag) = null_flags(nulls);
564    CanonicalKey(Bytes::from_static(match non_null_flag {
565        0 => &[0],
566        _ => &[1],
567    }))
568}
569
570/// Canonically encode an index's ordered columns into a [`CanonicalKey`].
571///
572/// Each column contributes a self-delimiting, order-preserving blob: a
573/// leading flag byte separating NULL from non-null (placed per the column's
574/// [`NullOrder`], never complemented, so null placement is independent of
575/// direction), then — for a non-null value — its canonical bytes framed as a
576/// storekey byte string, complemented in full (terminator included) when the
577/// column is [`Direction::Descending`] so variable-length values reverse
578/// correctly. Concatenating the blobs makes byte order equal SQL tuple order.
579///
580/// Fails as [`Error::Constraint`] when the summed non-null value size exceeds
581/// [`MAX_INDEX_KEY_BYTES`] or an integer value does not fit its declared
582/// width (truncating would map distinct values to one key).
583#[cfg(test)]
584pub(crate) fn encode_ordered_key(columns: &[OrderedColumn]) -> Result<CanonicalKey> {
585    let mut builder = CanonicalKeyBuilder::new();
586    for column in columns {
587        builder.append(
588            column.value.as_ref().map(BorrowedIndexKeyValue::from),
589            column.direction,
590            column.nulls,
591        )?;
592    }
593    Ok(builder.finish().0)
594}
595
596/// Appends every value in its column's declared order; `directions` and
597/// `nulls` run parallel to `values`, defaulting to ascending / NULLS LAST
598/// where shorter.
599fn build(
600    values: &[Option<IndexKeyValue>],
601    directions: &[Direction],
602    nulls: &[NullOrder],
603    mut builder: CanonicalKeyBuilder,
604) -> Result<CanonicalKeyBuilder> {
605    for (index, value) in values.iter().enumerate() {
606        builder.append(
607            value.as_ref().map(BorrowedIndexKeyValue::from),
608            directions
609                .get(index)
610                .copied()
611                .unwrap_or(Direction::Ascending),
612            nulls.get(index).copied().unwrap_or(NullOrder::Last),
613        )?;
614    }
615    Ok(builder)
616}
617
618/// Encode index values in their columns' declared orders. `directions` and
619/// `nulls` run parallel to `values`, defaulting to ascending / NULLS LAST
620/// where shorter. A `None` value is SQL NULL.
621pub(crate) fn encode_ordered_values(
622    values: &[Option<IndexKeyValue>],
623    directions: &[Direction],
624    nulls: &[NullOrder],
625) -> Result<CanonicalKey> {
626    Ok(
627        build(values, directions, nulls, CanonicalKeyBuilder::new())?
628            .finish()
629            .0,
630    )
631}
632
633/// Encodes ordered values straight into their physical SlateDB entry key.
634/// Returns whether the result uses the unique shape; any NULL forces the
635/// multi shape even when `requested_unique` is true.
636pub(crate) fn encode_ordered_index_entry(
637    values: &[Option<IndexKeyValue>],
638    directions: &[Direction],
639    nulls: &[NullOrder],
640    index_id: u64,
641    requested_unique: bool,
642    row_id: u64,
643) -> Result<(Bytes, bool)> {
644    Ok(build(
645        values,
646        directions,
647        nulls,
648        CanonicalKeyBuilder::for_index_entry(requested_unique),
649    )?
650    .finish_index_entry(index_id, requested_unique, row_id))
651}
652
653/// A single slice framed as a storekey byte string: low bytes escaped behind
654/// `0x01`, a `0x00` terminator — order-preserving and prefix-free, so a
655/// shorter value sorts before its extension and component boundaries stay
656/// unambiguous under concatenation.
657#[cfg(test)]
658fn frame_bytes(raw: &[u8]) -> Vec<u8> {
659    // Infallible by construction: a `Vec` sink raises no io error and
660    // storekey's `Vec<u8>` encoder (a byte string, not the generic sequence
661    // codec) raises no custom error.
662    #[allow(clippy::expect_used)]
663    storekey::encode_vec(&raw.to_vec()).expect("storekey encode into a Vec cannot fail")
664}
665
666#[cfg(test)]
667mod tests {
668    use super::*;
669
670    /// The allocation-heavy encoder shipped before direct key construction.
671    /// Keeping it test-only pins the new builder to the already persisted
672    /// format independently of the production implementation.
673    fn legacy_encode_ordered_key(columns: &[OrderedColumn]) -> Result<CanonicalKey> {
674        let mut total = 0usize;
675        let mut out = Vec::new();
676        for column in columns {
677            let (null_flag, non_null_flag) = null_flags(column.nulls);
678            let Some(value) = &column.value else {
679                out.push(null_flag);
680                continue;
681            };
682            BorrowedIndexKeyValue::from(value).validate_width()?;
683            let raw = value.encode();
684            total = total.saturating_add(raw.len());
685            let framed = frame_bytes(&raw);
686            out.push(non_null_flag);
687            match column.direction {
688                Direction::Ascending => out.extend_from_slice(&framed),
689                Direction::Descending => out.extend(framed.iter().map(|byte| !byte)),
690            }
691        }
692        if total > MAX_INDEX_KEY_BYTES {
693            return Err(Error::Constraint(format!(
694                "index key of {total} bytes exceeds the {MAX_INDEX_KEY_BYTES}-byte limit"
695            )));
696        }
697        Ok(CanonicalKey(Bytes::from(out)))
698    }
699
700    fn build_borrowed(columns: &[OrderedColumn]) -> Result<(CanonicalKey, bool)> {
701        let mut builder = CanonicalKeyBuilder::new();
702        for column in columns {
703            builder.append(
704                column.value.as_ref().map(BorrowedIndexKeyValue::from),
705                column.direction,
706                column.nulls,
707            )?;
708        }
709        Ok(builder.finish())
710    }
711
712    #[test]
713    fn golden_signed_int_flips_sign_bit() {
714        // Zero sits at the midpoint; the high bit is set.
715        assert_eq!(
716            IndexKeyValue::Int {
717                value: 0,
718                width: IntWidth::I64,
719            }
720            .encode(),
721            vec![0x80, 0, 0, 0, 0, 0, 0, 0],
722        );
723        // One is just above the midpoint.
724        assert_eq!(
725            IndexKeyValue::Int {
726                value: 1,
727                width: IntWidth::I64,
728            }
729            .encode(),
730            vec![0x80, 0, 0, 0, 0, 0, 0, 1],
731        );
732        // Minus one sits just below the midpoint.
733        assert_eq!(
734            IndexKeyValue::Int {
735                value: -1,
736                width: IntWidth::I64,
737            }
738            .encode(),
739            vec![0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
740        );
741    }
742
743    #[test]
744    fn golden_signed_int_widths_are_normalized() {
745        assert_eq!(
746            IndexKeyValue::Int {
747                value: 1,
748                width: IntWidth::I8,
749            }
750            .encode(),
751            vec![0x81],
752        );
753        assert_eq!(
754            IndexKeyValue::Int {
755                value: 1,
756                width: IntWidth::I16,
757            }
758            .encode(),
759            vec![0x80, 0x01],
760        );
761        assert_eq!(
762            IndexKeyValue::Int {
763                value: 1,
764                width: IntWidth::I32,
765            }
766            .encode(),
767            vec![0x80, 0, 0, 0x01],
768        );
769        assert_eq!(
770            IndexKeyValue::Int {
771                value: 1,
772                width: IntWidth::I128,
773            }
774            .encode()
775            .len(),
776            16,
777        );
778    }
779
780    #[test]
781    fn golden_unsigned_int_is_plain_big_endian() {
782        assert_eq!(
783            IndexKeyValue::UInt {
784                value: 5,
785                width: IntWidth::I32,
786            }
787            .encode(),
788            vec![0, 0, 0, 5],
789        );
790        assert_eq!(
791            IndexKeyValue::UInt {
792                value: u128::from(u64::MAX),
793                width: IntWidth::I64,
794            }
795            .encode(),
796            vec![0xff; 8],
797        );
798    }
799
800    // Equality and inequality of the encoding, and — for the widths where
801    // it is free — order-compatibility with the numeric order.
802
803    fn signed(value: i128) -> Vec<u8> {
804        IndexKeyValue::Int {
805            value,
806            width: IntWidth::I64,
807        }
808        .encode()
809    }
810
811    #[test]
812    fn equal_values_encode_equally_distinct_values_distinctly() {
813        assert_eq!(signed(42), signed(42));
814        assert_ne!(signed(42), signed(43));
815    }
816
817    #[test]
818    fn signed_encoding_is_order_compatible() {
819        let ordered = [i64::MIN, -2, -1, 0, 1, 2, i64::MAX];
820        for pair in ordered.windows(2) {
821            let lower = signed(i128::from(pair[0]));
822            let higher = signed(i128::from(pair[1]));
823            assert!(lower < higher, "{:?} !< {:?}", pair[0], pair[1]);
824        }
825    }
826
827    #[test]
828    fn unsigned_encoding_is_order_compatible() {
829        let value = |v: u128| {
830            IndexKeyValue::UInt {
831                value: v,
832                width: IntWidth::I64,
833            }
834            .encode()
835        };
836        assert!(value(0) < value(1));
837        assert!(value(1) < value(u128::from(u64::MAX)));
838    }
839
840    #[test]
841    fn golden_bool_is_one_byte() {
842        assert_eq!(IndexKeyValue::Bool(false).encode(), vec![0]);
843        assert_eq!(IndexKeyValue::Bool(true).encode(), vec![1]);
844    }
845
846    #[test]
847    fn float_negative_zero_normalizes_to_positive_zero() {
848        assert_eq!(
849            IndexKeyValue::F64(-0.0).encode(),
850            IndexKeyValue::F64(0.0).encode(),
851        );
852        assert_eq!(
853            IndexKeyValue::F32(-0.0).encode(),
854            IndexKeyValue::F32(0.0).encode(),
855        );
856    }
857
858    #[test]
859    fn float_nans_collapse_to_one_pattern() {
860        // Distinct NaN bit patterns (quiet, signalling, sign-set) all
861        // encode identically — DuckDB treats NaN = NaN.
862        let quiet = IndexKeyValue::F64(f64::from_bits(0x7ff8_0000_0000_0001)).encode();
863        let signalling = IndexKeyValue::F64(f64::from_bits(0xfff0_0000_0000_0001)).encode();
864        let canonical = IndexKeyValue::F64(f64::NAN).encode();
865        assert_eq!(quiet, signalling);
866        assert_eq!(signalling, canonical);
867
868        let f32_quiet = IndexKeyValue::F32(f32::from_bits(0x7fc0_0001)).encode();
869        let f32_signed = IndexKeyValue::F32(f32::from_bits(0xffc0_0001)).encode();
870        assert_eq!(f32_quiet, f32_signed);
871    }
872
873    #[test]
874    fn distinct_floats_encode_distinctly() {
875        assert_ne!(
876            IndexKeyValue::F64(1.0).encode(),
877            IndexKeyValue::F64(2.0).encode(),
878        );
879        assert_ne!(
880            IndexKeyValue::F32(1.5).encode(),
881            IndexKeyValue::F32(-1.5).encode(),
882        );
883    }
884
885    #[test]
886    fn golden_string_and_blob_are_raw_bytes() {
887        assert_eq!(IndexKeyValue::Str("abc".into()).encode(), b"abc".to_vec(),);
888        assert_eq!(
889            IndexKeyValue::Bytes(vec![0, 1, 2, 0xff]).encode(),
890            vec![0, 1, 2, 0xff],
891        );
892    }
893
894    #[test]
895    fn borrowed_builder_matches_legacy_for_escape_bytes_floats_and_nulls() {
896        let columns = [
897            OrderedColumn {
898                value: Some(IndexKeyValue::Str("a\0\u{1}z".to_owned())),
899                direction: Direction::Descending,
900                nulls: NullOrder::First,
901            },
902            OrderedColumn {
903                value: Some(IndexKeyValue::Bytes(vec![0, 1, 0xff])),
904                direction: Direction::Ascending,
905                nulls: NullOrder::Last,
906            },
907            OrderedColumn {
908                value: Some(IndexKeyValue::F64(-0.0)),
909                direction: Direction::Ascending,
910                nulls: NullOrder::Last,
911            },
912            OrderedColumn {
913                value: Some(IndexKeyValue::F32(f32::from_bits(0xffc0_0001))),
914                direction: Direction::Descending,
915                nulls: NullOrder::First,
916            },
917            OrderedColumn {
918                value: None,
919                direction: Direction::Descending,
920                nulls: NullOrder::Last,
921            },
922        ];
923
924        let (built, has_null) = build_borrowed(&columns).unwrap();
925        assert_eq!(built, legacy_encode_ordered_key(&columns).unwrap());
926        assert!(has_null);
927    }
928
929    #[test]
930    fn encode_key_is_deterministic_and_injective() {
931        let value = |s: &str| {
932            encode_key(&[
933                IndexKeyValue::Int {
934                    value: 7,
935                    width: IntWidth::I64,
936                },
937                IndexKeyValue::Str(s.into()),
938            ])
939            .unwrap()
940        };
941        assert_eq!(value("x"), value("x"));
942        assert_ne!(value("x"), value("y"));
943        // Framing produces non-empty bytes even for a short key.
944        assert!(!value("x").as_bytes().is_empty());
945    }
946
947    #[test]
948    fn composite_framing_distinguishes_component_splits() {
949        // The classic ambiguity: without framing, ("ab","c") and
950        // ("a","bc") would share a byte string. The storekey framing must
951        // keep them distinct.
952        let ab_c = encode_key(&[
953            IndexKeyValue::Str("ab".into()),
954            IndexKeyValue::Str("c".into()),
955        ])
956        .unwrap();
957        let a_bc = encode_key(&[
958            IndexKeyValue::Str("a".into()),
959            IndexKeyValue::Str("bc".into()),
960        ])
961        .unwrap();
962        assert_ne!(ab_c, a_bc);
963    }
964
965    #[test]
966    fn oversized_key_is_refused() {
967        let big = IndexKeyValue::Bytes(vec![0; MAX_INDEX_KEY_BYTES + 1]);
968        let err = encode_key(std::slice::from_ref(&big)).unwrap_err();
969        assert!(matches!(err, Error::Constraint(_)), "got {err:?}");
970    }
971
972    #[test]
973    fn key_at_the_cap_is_accepted() {
974        let at_cap = IndexKeyValue::Bytes(vec![0; MAX_INDEX_KEY_BYTES]);
975        assert!(encode_key(std::slice::from_ref(&at_cap)).is_ok());
976    }
977
978    #[test]
979    fn out_of_width_integers_are_refused() {
980        // Truncating 300 to one byte would collide with 44; refuse instead.
981        let narrow = |value| {
982            encode_key(&[IndexKeyValue::Int {
983                value,
984                width: IntWidth::I8,
985            }])
986        };
987        assert!(matches!(narrow(300).unwrap_err(), Error::Constraint(_)));
988        assert!(matches!(narrow(-129).unwrap_err(), Error::Constraint(_)));
989        assert!(narrow(i128::from(i8::MIN)).is_ok());
990        assert!(narrow(i128::from(i8::MAX)).is_ok());
991
992        let unsigned = |value| {
993            encode_key(&[IndexKeyValue::UInt {
994                value,
995                width: IntWidth::I8,
996            }])
997        };
998        assert!(matches!(unsigned(256).unwrap_err(), Error::Constraint(_)));
999        assert!(unsigned(u128::from(u8::MAX)).is_ok());
1000
1001        // Full width admits the extremes.
1002        assert!(
1003            encode_key(&[IndexKeyValue::Int {
1004                value: i128::MIN,
1005                width: IntWidth::I128,
1006            }])
1007            .is_ok()
1008        );
1009        assert!(
1010            encode_key(&[IndexKeyValue::UInt {
1011                value: u128::MAX,
1012                width: IntWidth::I128,
1013            }])
1014            .is_ok()
1015        );
1016    }
1017
1018    fn int(value: i128) -> IndexKeyValue {
1019        IndexKeyValue::Int {
1020            value,
1021            width: IntWidth::I64,
1022        }
1023    }
1024
1025    fn ordered(value: Option<IndexKeyValue>, direction: Direction, nulls: NullOrder) -> Vec<u8> {
1026        encode_ordered_key(&[OrderedColumn {
1027            value,
1028            direction,
1029            nulls,
1030        }])
1031        .unwrap()
1032        .as_bytes()
1033        .to_vec()
1034    }
1035
1036    #[test]
1037    fn float_encoding_is_order_preserving() {
1038        let enc = |v: f64| IndexKeyValue::F64(v).encode();
1039        let rising = [
1040            f64::NEG_INFINITY,
1041            -1e300,
1042            -1.0,
1043            -0.0,
1044            0.0,
1045            1.0,
1046            1e300,
1047            f64::INFINITY,
1048        ];
1049        for pair in rising.windows(2) {
1050            assert!(enc(pair[0]) <= enc(pair[1]), "{} !<= {}", pair[0], pair[1]);
1051        }
1052        // Strict where the values differ, and NaN sorts greatest of all.
1053        assert!(enc(-1.0) < enc(1.0));
1054        assert!(enc(f64::NEG_INFINITY) < enc(-1e300));
1055        assert!(enc(f64::INFINITY) < enc(f64::NAN));
1056        // -0.0 and 0.0 collapse to one encoding.
1057        assert_eq!(enc(-0.0), enc(0.0));
1058    }
1059
1060    #[test]
1061    fn descending_reverses_numeric_order() {
1062        let asc = |v: i128| ordered(Some(int(v)), Direction::Ascending, NullOrder::Last);
1063        let desc = |v: i128| ordered(Some(int(v)), Direction::Descending, NullOrder::Last);
1064        assert!(asc(1) < asc(2));
1065        // The larger value sorts first under descending.
1066        assert!(desc(2) < desc(1));
1067    }
1068
1069    #[test]
1070    fn descending_reverses_variable_length_strings() {
1071        let string = |s: &str| IndexKeyValue::Str(s.to_owned());
1072        let asc = |s: &str| ordered(Some(string(s)), Direction::Ascending, NullOrder::Last);
1073        let desc = |s: &str| ordered(Some(string(s)), Direction::Descending, NullOrder::Last);
1074        // Ascending "a" < "ab"; descending must reverse it, length included —
1075        // the property that breaks if only the raw bytes are complemented.
1076        assert!(asc("a") < asc("ab"));
1077        assert!(desc("ab") < desc("a"));
1078    }
1079
1080    #[test]
1081    fn null_placement_respects_first_and_last_under_both_directions() {
1082        for direction in [Direction::Ascending, Direction::Descending] {
1083            let null_first = ordered(None, direction, NullOrder::First);
1084            let value_first = ordered(Some(int(0)), direction, NullOrder::First);
1085            assert!(null_first < value_first, "NULLS FIRST under {direction:?}");
1086
1087            let null_last = ordered(None, direction, NullOrder::Last);
1088            let value_last = ordered(Some(int(0)), direction, NullOrder::Last);
1089            assert!(value_last < null_last, "NULLS LAST under {direction:?}");
1090        }
1091    }
1092
1093    #[test]
1094    fn composite_order_matches_declared_per_column_directions() {
1095        // Index (a ASC, b DESC).
1096        let key = |a: i128, b: i128| {
1097            encode_ordered_key(&[
1098                OrderedColumn {
1099                    value: Some(int(a)),
1100                    direction: Direction::Ascending,
1101                    nulls: NullOrder::Last,
1102                },
1103                OrderedColumn {
1104                    value: Some(int(b)),
1105                    direction: Direction::Descending,
1106                    nulls: NullOrder::Last,
1107                },
1108            ])
1109            .unwrap()
1110        };
1111        // `a` ascending is the primary sort, whatever `b` holds.
1112        assert!(key(1, 100) < key(2, 100));
1113        assert!(key(1, 0) < key(2, 100));
1114        // Within one `a`, `b` sorts descending.
1115        assert!(key(1, 100) < key(1, 50));
1116        assert!(key(1, 50) < key(1, 10));
1117    }
1118
1119    #[test]
1120    fn ordered_encoding_is_deterministic_for_uniqueness() {
1121        // A unique index keys on the value alone; the same value must encode
1122        // identically under any direction so racing inserts collide.
1123        let one = ordered(Some(int(7)), Direction::Descending, NullOrder::First);
1124        assert_eq!(
1125            one,
1126            ordered(Some(int(7)), Direction::Descending, NullOrder::First)
1127        );
1128        assert_ne!(
1129            one,
1130            ordered(Some(int(8)), Direction::Descending, NullOrder::First)
1131        );
1132    }
1133
1134    mod properties {
1135        use proptest::prelude::*;
1136
1137        use super::*;
1138
1139        fn index_value() -> impl Strategy<Value = IndexKeyValue> {
1140            prop_oneof![
1141                any::<i8>().prop_map(|value| IndexKeyValue::Int {
1142                    value: i128::from(value),
1143                    width: IntWidth::I8,
1144                }),
1145                any::<i16>().prop_map(|value| IndexKeyValue::Int {
1146                    value: i128::from(value),
1147                    width: IntWidth::I16,
1148                }),
1149                any::<i32>().prop_map(|value| IndexKeyValue::Int {
1150                    value: i128::from(value),
1151                    width: IntWidth::I32,
1152                }),
1153                any::<i64>().prop_map(|value| IndexKeyValue::Int {
1154                    value: i128::from(value),
1155                    width: IntWidth::I64,
1156                }),
1157                any::<i128>().prop_map(|value| IndexKeyValue::Int {
1158                    value,
1159                    width: IntWidth::I128,
1160                }),
1161                any::<u8>().prop_map(|value| IndexKeyValue::UInt {
1162                    value: u128::from(value),
1163                    width: IntWidth::I8,
1164                }),
1165                any::<u16>().prop_map(|value| IndexKeyValue::UInt {
1166                    value: u128::from(value),
1167                    width: IntWidth::I16,
1168                }),
1169                any::<u32>().prop_map(|value| IndexKeyValue::UInt {
1170                    value: u128::from(value),
1171                    width: IntWidth::I32,
1172                }),
1173                any::<u64>().prop_map(|value| IndexKeyValue::UInt {
1174                    value: u128::from(value),
1175                    width: IntWidth::I64,
1176                }),
1177                any::<u128>().prop_map(|value| IndexKeyValue::UInt {
1178                    value,
1179                    width: IntWidth::I128,
1180                }),
1181                any::<u32>().prop_map(|bits| IndexKeyValue::F32(f32::from_bits(bits))),
1182                any::<u64>().prop_map(|bits| IndexKeyValue::F64(f64::from_bits(bits))),
1183                any::<bool>().prop_map(IndexKeyValue::Bool),
1184                ".{0,32}".prop_map(IndexKeyValue::Str),
1185                prop::collection::vec(any::<u8>(), 0..32).prop_map(IndexKeyValue::Bytes),
1186            ]
1187        }
1188
1189        fn ordered_column() -> impl Strategy<Value = OrderedColumn> {
1190            (
1191                prop::option::of(index_value()),
1192                any::<bool>(),
1193                any::<bool>(),
1194            )
1195                .prop_map(|(value, descending, nulls_first)| OrderedColumn {
1196                    value,
1197                    direction: if descending {
1198                        Direction::Descending
1199                    } else {
1200                        Direction::Ascending
1201                    },
1202                    nulls: if nulls_first {
1203                        NullOrder::First
1204                    } else {
1205                        NullOrder::Last
1206                    },
1207                })
1208        }
1209
1210        proptest! {
1211            /// Direct borrowed construction is byte-for-byte identical to
1212            /// the encoder that produced existing persistent index keys.
1213            #[test]
1214            fn borrowed_builder_matches_legacy_encoder(
1215                columns in prop::collection::vec(ordered_column(), 0..8),
1216            ) {
1217                let expected = legacy_encode_ordered_key(&columns).unwrap();
1218                let (actual, has_null) = build_borrowed(&columns).unwrap();
1219                prop_assert_eq!(actual, expected);
1220                prop_assert_eq!(has_null, columns.iter().any(|column| column.value.is_none()));
1221            }
1222
1223            /// In-place finalization is byte-for-byte identical to the
1224            /// persisted `Key::Index` representation, including NULL's
1225            /// forced multi shape and an arbitrary raw row-id suffix.
1226            #[test]
1227            fn physical_builder_matches_existing_entry_encoder(
1228                columns in prop::collection::vec(ordered_column(), 0..8),
1229                index_id in any::<u64>(),
1230                requested_unique in any::<bool>(),
1231                row_id in any::<u64>(),
1232            ) {
1233                let canonical = legacy_encode_ordered_key(&columns).unwrap();
1234                let actual_unique = requested_unique
1235                    && columns.iter().all(|column| column.value.is_some());
1236                let expected = crate::store::key::encode_index_entry(
1237                    index_id,
1238                    actual_unique,
1239                    &canonical,
1240                    row_id,
1241                );
1242                let mut builder = CanonicalKeyBuilder::for_index_entry(requested_unique);
1243                for column in &columns {
1244                    builder.append(
1245                        column.value.as_ref().map(BorrowedIndexKeyValue::from),
1246                        column.direction,
1247                        column.nulls,
1248                    ).unwrap();
1249                }
1250                let (actual, unique) = builder.finish_index_entry(
1251                    index_id,
1252                    requested_unique,
1253                    row_id,
1254                );
1255
1256                prop_assert_eq!(actual.as_ref(), expected.as_slice());
1257                prop_assert_eq!(unique, actual_unique);
1258            }
1259
1260            /// Determinism and injectivity: equal values share one byte
1261            /// string, distinct values never collide.
1262            #[test]
1263            fn signed_encoding_is_injective(a: i64, b: i64) {
1264                let one = signed(i128::from(a));
1265                let two = signed(i128::from(b));
1266                prop_assert_eq!(one.clone(), signed(i128::from(a)));
1267                prop_assert_eq!(a == b, one == two);
1268            }
1269
1270            /// Byte order matches numeric order at every width the codec
1271            /// promises order-compatibility for.
1272            #[test]
1273            fn signed_encoding_preserves_order(a: i64, b: i64) {
1274                let one = signed(i128::from(a));
1275                let two = signed(i128::from(b));
1276                prop_assert_eq!(a.cmp(&b), one.cmp(&two));
1277            }
1278
1279            #[test]
1280            fn unsigned_encoding_preserves_order(a: u64, b: u64) {
1281                let encode = |value| {
1282                    IndexKeyValue::UInt {
1283                        value,
1284                        width: IntWidth::I64,
1285                    }
1286                    .encode()
1287                };
1288                let one = encode(u128::from(a));
1289                let two = encode(u128::from(b));
1290                prop_assert_eq!(a.cmp(&b), one.cmp(&two));
1291            }
1292
1293            /// Non-NaN float bytes sort in numeric total order, with -0.0 and
1294            /// +0.0 collapsed to one value.
1295            #[test]
1296            fn float_encoding_preserves_total_order(a: f64, b: f64) {
1297                prop_assume!(!a.is_nan() && !b.is_nan());
1298                let one = IndexKeyValue::F64(a).encode();
1299                let two = IndexKeyValue::F64(b).encode();
1300                let norm = |v: f64| if v == 0.0 { 0.0 } else { v };
1301                let expected = norm(a).partial_cmp(&norm(b)).unwrap();
1302                prop_assert_eq!(expected, one.cmp(&two));
1303            }
1304
1305            /// In-range narrow values encode injectively — the property the
1306            /// width validation in `encode_key` protects.
1307            #[test]
1308            fn narrow_signed_encoding_is_injective(a: i16, b: i16) {
1309                let encode = |value: i16| {
1310                    encode_key(&[IndexKeyValue::Int {
1311                        value: i128::from(value),
1312                        width: IntWidth::I16,
1313                    }])
1314                    .unwrap()
1315                };
1316                prop_assert_eq!(a == b, encode(a) == encode(b));
1317            }
1318
1319            /// Composite framing keeps distinct component splits distinct
1320            /// for arbitrary string pairs.
1321            #[test]
1322            fn composite_framing_is_injective(
1323                a in prop::collection::vec(".{0,8}", 1..3),
1324                b in prop::collection::vec(".{0,8}", 1..3),
1325            ) {
1326                let encode = |parts: &[String]| {
1327                    encode_key(
1328                        &parts
1329                            .iter()
1330                            .map(|part| IndexKeyValue::Str(part.clone()))
1331                            .collect::<Vec<_>>(),
1332                    )
1333                    .unwrap()
1334                };
1335                prop_assert_eq!(a == b, encode(&a) == encode(&b));
1336            }
1337
1338            /// The nominal size brackets the encoding it estimates: never
1339            /// over the real length, and never under half of it, since
1340            /// framing escapes at most every byte of a value.
1341            #[test]
1342            fn nominal_size_brackets_the_encoded_key(
1343                values in prop::collection::vec(
1344                    prop::option::of(prop::collection::vec(any::<u8>(), 0..24)),
1345                    0..4,
1346                ),
1347            ) {
1348                let values: Vec<Option<IndexKeyValue>> = values
1349                    .into_iter()
1350                    .map(|value| value.map(IndexKeyValue::Bytes))
1351                    .collect();
1352                let columns: Vec<OrderedColumn> = values
1353                    .iter()
1354                    .map(|value| OrderedColumn {
1355                        value: value.clone(),
1356                        direction: Direction::Ascending,
1357                        nulls: NullOrder::Last,
1358                    })
1359                    .collect();
1360
1361                let nominal = nominal_key_bytes(&values);
1362                let encoded = encode_ordered_key(&columns).unwrap().as_bytes().len();
1363                prop_assert!(nominal <= encoded, "{nominal} > {encoded}");
1364                prop_assert!(encoded <= 2 * nominal, "{encoded} > 2 * {nominal}");
1365            }
1366        }
1367    }
1368}