Skip to main content

mlt_core/decoder/stream/
model.rs

1use std::fmt::{Display, Formatter, Result as FmtResult};
2
3use derive_debug::Dbg;
4use num_enum::TryFromPrimitive;
5
6use crate::utils::formatter::{bytes_dbg, compact_dbg};
7use crate::{MltError, MltResult};
8
9/// Logical encoding technique used for a column, as stored in the tile
10///
11/// Variants are already shifted into the primary logical field of the v1 encoding byte (bits 7-5),
12/// so that field is matched with a mask rather than shifted down first.
13/// The secondary field (bits 4-2) holds the same patterns three bits lower.
14#[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)]
15#[repr(u8)]
16pub enum LogicalTechnique {
17    None = 0b0000_0000,
18    Delta = 0b0010_0000,
19    ComponentwiseDelta = 0b0100_0000,
20    Rle = 0b0110_0000,
21    Morton = 0b1000_0000,
22}
23
24/// The combinations of the two [`LogicalTechnique`] fields that are legal on the wire
25///
26/// Each variant is the whole logical part of the v1 encoding byte, i.e. the primary field
27/// (bits 7-5) or-ed with the secondary field (bits 4-2).
28/// Any other pairing of the two fields is rejected while parsing.
29#[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)]
30#[repr(u8)]
31pub enum LogicalCombination {
32    None = 0b0000_0000,
33    Delta = 0b0010_0000,
34    DeltaRle = 0b0010_1100,
35    ComponentwiseDelta = 0b0100_0000,
36    Rle = 0b0110_0000,
37    Morton = 0b1000_0000,
38    MortonDelta = 0b1000_0100,
39    MortonRle = 0b1000_1100,
40}
41
42/// Which RLE stream layout the encoder should produce.
43///
44/// A data-less selector chosen up front by the wire format (see
45/// [`WireVersion::rle_layout`](crate::encoder::WireVersion)); the realized
46/// per-stream metadata is [`RleMeta`], whose variants mirror these.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum RleLayout {
49    /// Tag `0x01`: all run lengths first, then all values.
50    Split,
51    /// Tag `0x02`: `(run_length, value)` pairs. Requires the `unstable-v2` feature.
52    #[cfg(feature = "unstable-v2")]
53    Interleaved,
54}
55
56/// Metadata for RLE decoding, one variant per [`RleLayout`].
57#[derive(Debug, Clone, Copy, PartialEq)]
58pub enum RleMeta {
59    /// Tag `0x01`: physically-decoded words are `[run_len × runs][value × runs]`.
60    /// `runs` is the split point; `num_rle_values` is the expanded element count.
61    Split { runs: u32, num_rle_values: u32 },
62    /// Tag `0x02`: physically-decoded words are `(run_len, value)` pairs. The run
63    /// count is derived from the data length, so only the expanded element count
64    /// (`num_rle_values`, from the stream's count context) is carried.
65    /// Requires the `unstable-v2` feature.
66    #[cfg(feature = "unstable-v2")]
67    Interleaved { num_rle_values: u32 },
68}
69
70impl RleMeta {
71    /// The total expanded element count, common to both layouts.
72    #[cfg(feature = "unstable-v2")]
73    #[must_use]
74    pub(crate) fn num_rle_values(self) -> u32 {
75        match self {
76            Self::Split { num_rle_values, .. } => num_rle_values,
77            #[cfg(feature = "unstable-v2")]
78            Self::Interleaved { num_rle_values } => num_rle_values,
79        }
80    }
81}
82
83/// Metadata for Morton decoding
84#[derive(Debug, Clone, Copy, PartialEq)]
85pub struct Morton {
86    /// Number of bits used
87    pub(crate) bits: u32,
88    /// Coordinate shift
89    pub(crate) shift: u32,
90}
91
92impl Morton {
93    pub fn new(bits: u32, shift: u32) -> MltResult<Self> {
94        if bits <= 16 {
95            Ok(Self { bits, shift })
96        } else {
97            Err(MltError::InvalidMortonBits(bits))
98        }
99    }
100}
101
102/// The decimal scaling an ALP column uses: `i = round(v * 10^e / 10^f)`.
103/// Chosen before any value is seen, so it carries no frame of reference.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub struct AlpScale {
106    /// Decimal exponent the values were scaled by.
107    pub(crate) e: u8,
108    /// Factor dividing out the trailing zeros `e` introduced, never exceeding `e`.
109    pub(crate) f: u8,
110}
111
112/// ALP parameters: `v = (base + offset) * 10^f / 10^e`.
113/// Written as a scale byte and a base varint, the stream itself holding the unsigned offsets.
114#[derive(Clone, Copy, PartialEq, Eq)]
115pub struct Alp {
116    pub(crate) scale: AlpScale,
117    /// Frame of reference the offsets are measured from, the smallest scaled integer in the column.
118    pub(crate) base: i64,
119}
120
121#[cfg(feature = "unstable-v2")]
122impl AlpScale {
123    /// Largest exponent the codes can carry, past which `v * 10^e` leaves the `i64` range.
124    /// The codes themselves are bounded tighter, to `2^53 - 1`, by the encoder.
125    pub(crate) const MAX_EXPONENT: u8 = 18;
126
127    /// Net power of ten the codes carry, which fixes their magnitude and so their stored size.
128    /// The order [`candidates`](crate::codecs::alp::candidates) walks, and so what lets the
129    /// encoder stop at the first scale that fits; only the tests holding that invariant name it.
130    #[cfg(test)]
131    pub(crate) fn net(self) -> u8 {
132        self.e - self.f
133    }
134
135    /// The header byte naming this scale, the pairs with `f <= e` numbered row by row.
136    pub(crate) fn to_byte(self) -> u8 {
137        Self::row_start(self.e) + self.f
138    }
139
140    /// Read a scale back from its header byte, rejecting the codes past the last pair.
141    pub(crate) fn from_byte(byte: u8) -> MltResult<Self> {
142        (0..=Self::MAX_EXPONENT)
143            .find_map(|e| {
144                let f = byte.checked_sub(Self::row_start(e))?;
145                (f <= e).then_some(Self { e, f })
146            })
147            .ok_or(MltError::InvalidAlpScale(byte))
148    }
149
150    /// First byte of the row for `e`, which is `e * (e + 1) / 2`.
151    fn row_start(e: u8) -> u8 {
152        (1..=e).sum()
153    }
154}
155
156/// Flattened, since the nesting is an encoder concern and these appear in stream labels.
157impl std::fmt::Debug for Alp {
158    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
159        write!(
160            f,
161            "Alp {{ e: {}, f: {}, base: {} }}",
162            self.scale.e, self.scale.f, self.base
163        )
164    }
165}
166
167#[cfg(feature = "unstable-v2")]
168impl Alp {
169    #[cfg(test)]
170    pub(crate) fn new(e: u8, f: u8, base: i64) -> MltResult<Self> {
171        if e <= AlpScale::MAX_EXPONENT && f <= e {
172            Ok(Self {
173                scale: AlpScale { e, f },
174                base,
175            })
176        } else {
177            Err(MltError::InvalidAlpParams(e, f))
178        }
179    }
180
181    /// Measure a scaled integer from the frame of reference, giving the offset the stream stores.
182    /// Wrapping, so a foreign stream whose codes span the whole `i64` range still subtracts
183    /// exactly; our own encoder keeps codes within `2^53 - 1`, so the spread fits `u64` easily.
184    #[expect(
185        clippy::cast_sign_loss,
186        reason = "the bit pattern is the point; `code_at` casts it back"
187    )]
188    pub(crate) fn offset_of(self, code: i64) -> u64 {
189        (code as u64).wrapping_sub(self.base as u64)
190    }
191
192    /// Put a stored offset back on the frame of reference, inverting [`Self::offset_of`].
193    #[expect(
194        clippy::cast_possible_wrap,
195        clippy::cast_sign_loss,
196        reason = "the bit pattern is the point; inverts `offset_of` exactly"
197    )]
198    pub(crate) fn code_at(self, offset: u64) -> i64 {
199        (self.base as u64).wrapping_add(offset) as i64
200    }
201}
202
203/// What kind of values a stream holds, which fixes the encodings it can name.
204/// Neither wire format stores it: v1 reads it from the column type, v2 from the stream's context.
205#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206pub enum ValueKind {
207    Int,
208    Bool,
209    Float,
210    Vertex,
211}
212
213/// Logical encoding of a stream of integer values.
214/// Covers the id columns, the integer property columns, and the geometry length and offset streams.
215#[derive(Debug, Clone, Copy, PartialEq)]
216pub enum IntLogical {
217    None,
218    Delta,
219    Rle(RleMeta),
220    DeltaRle(RleMeta),
221}
222
223/// Logical encoding of a bool column's data stream or a presence bitfield.
224#[derive(Debug, Clone, Copy, PartialEq)]
225pub enum BoolLogical {
226    /// A raw packed bitmap, one bit per value.
227    None,
228    /// A byte-RLE compressed bitmap.
229    /// Its run parameters come from the stream's context rather than from its header.
230    ByteRle(RleMeta),
231}
232
233/// Logical encoding of a float column's data stream.
234#[derive(Debug, Clone, Copy, PartialEq)]
235pub enum FloatLogical {
236    /// Fixed-width little-endian values, one per element.
237    None,
238    /// A decimal split across two integer streams, which v1 can name but no decoder here implements.
239    /// One code per element, with the distinct values following as a second stream.
240    /// Only the tag `0x02` codec reads or writes it.
241    Dict,
242    /// Integers scaled by these parameters.
243    /// Only the tag `0x02` codec reads or writes it.
244    Alp(Alp),
245}
246
247/// Logical encoding of a geometry vertex stream, whose values are interleaved coordinate pairs.
248#[derive(Debug, Clone, Copy, PartialEq)]
249pub enum VertexLogical {
250    None,
251    Delta,
252    ComponentwiseDelta,
253    Morton(Morton),
254    MortonDelta(Morton),
255    MortonRle(Morton),
256}
257
258/// How should the stream be interpreted at the logical level (second pass of decoding)
259///
260/// Split per [`ValueKind`] so a stream can name only the encodings its values can have.
261#[derive(Debug, Clone, Copy, PartialEq)]
262pub enum LogicalEncoding {
263    Int(IntLogical),
264    Bool(BoolLogical),
265    Float(FloatLogical),
266    Vertex(VertexLogical),
267}
268
269impl LogicalEncoding {
270    /// The kind of values this encoding belongs to.
271    #[must_use]
272    pub fn kind(self) -> ValueKind {
273        match self {
274            Self::Int(_) => ValueKind::Int,
275            Self::Bool(_) => ValueKind::Bool,
276            Self::Float(_) => ValueKind::Float,
277            Self::Vertex(_) => ValueKind::Vertex,
278        }
279    }
280
281    /// The identity encoding for `kind`, i.e. values stored as they are.
282    #[must_use]
283    pub(crate) fn none(kind: ValueKind) -> Self {
284        match kind {
285            ValueKind::Int => Self::Int(IntLogical::None),
286            ValueKind::Bool => Self::Bool(BoolLogical::None),
287            ValueKind::Float => Self::Float(FloatLogical::None),
288            ValueKind::Vertex => Self::Vertex(VertexLogical::None),
289        }
290    }
291
292    /// Whether the stream's own logical pass is a no-op, so the physical words are already the output.
293    /// True for a float dictionary's codes, which the column turns back into floats.
294    /// Not true for ALP, whose offsets still need the frame of reference added back.
295    #[must_use]
296    pub(crate) fn is_identity(self) -> bool {
297        matches!(
298            self,
299            Self::Int(IntLogical::None)
300                | Self::Bool(BoolLogical::None)
301                | Self::Float(FloatLogical::None | FloatLogical::Dict)
302                | Self::Vertex(VertexLogical::None)
303        )
304    }
305}
306
307/// Carries the stream metadata needed to perform the logical decode pass.
308///
309/// Construct with [`LogicalValue::new`] after the physical decode pass fills a
310/// `&[u32]` or `&[u64]` buffer, then call the appropriate `decode_*` method,
311/// passing that slice as `data`.
312#[derive(Debug, PartialEq)]
313pub struct LogicalValue {
314    pub(crate) meta: StreamMeta,
315}
316
317// Physical encoding types
318
319/// Dictionary type used for a column, as stored in the tile
320#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TryFromPrimitive)]
321#[repr(u8)]
322pub enum DictionaryType {
323    None = 0b0000_0000,
324    Single = 0b0000_0001,
325    Shared = 0b0000_0010,
326    Vertex = 0b0000_0011,
327    Morton = 0b0000_0100,
328    Fsst = 0b0000_0101,
329}
330
331/// Offset type used for a column, as stored in the tile
332#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TryFromPrimitive)]
333#[repr(u8)]
334pub enum OffsetType {
335    Vertex = 0b0000_0000,
336    Index = 0b0000_0001,
337    String = 0b0000_0010,
338    Key = 0b0000_0011,
339}
340
341/// Length type used for a column, as stored in the tile
342#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, TryFromPrimitive)]
343#[repr(u8)]
344pub enum LengthType {
345    VarBinary = 0b0000_0000,
346    Geometries = 0b0000_0001,
347    Parts = 0b0000_0010,
348    Rings = 0b0000_0011,
349    Triangles = 0b0000_0100,
350    Symbol = 0b0000_0101,
351    Dictionary = 0b0000_0110,
352    /// A nested list or map node's lengths, one per present value of the node.
353    #[cfg(feature = "unstable-v2")]
354    Nested = 0b0000_0111,
355}
356
357/// How should the stream be interpreted at the physical level (first pass of decoding)
358#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
359pub enum StreamType {
360    Present,
361    Data(DictionaryType),
362    Offset(OffsetType),
363    Length(LengthType),
364}
365
366/// Physical encoding used for a column, as stored in the tile
367#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
368pub enum PhysicalEncoding {
369    None,
370    /// Preferred, tends to produce the best compression ratio and decoding performance.
371    /// But currently limited to 32-bit integer.
372    FastPFor(FastPForKind),
373    /// Can produce better results in combination with a heavyweight compression scheme like `Gzip`.
374    /// Simple compression scheme where the encoding is easier to implement compared to `FastPfor`.
375    VarInt,
376    /// Every value in the same number of bits, which a leading payload byte names.
377    /// Beats `VarInt` on the short, small-alphabet streams a dictionary's codes form,
378    /// where `FastPFor`'s 128-value blocks are too coarse to pay for themselves.
379    #[cfg(feature = "unstable-v2")]
380    BitPacked,
381}
382
383/// The `FastPFor` block size and word order a wire version codes its integer streams with.
384#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
385pub enum FastPForKind {
386    /// 256-value blocks over big-endian words
387    Block256Be,
388    /// 128-value blocks over little-endian words
389    #[cfg(feature = "unstable-v2")]
390    Block128Le,
391}
392
393// RawStream types
394
395#[derive(Debug, Clone, Copy, PartialEq)]
396pub struct IntEncoding {
397    pub logical: LogicalEncoding,
398    pub physical: PhysicalEncoding,
399}
400
401impl IntEncoding {
402    #[must_use]
403    pub(crate) const fn new(logical: LogicalEncoding, physical: PhysicalEncoding) -> Self {
404        Self { logical, physical }
405    }
406
407    #[must_use]
408    pub(crate) fn none(kind: ValueKind) -> Self {
409        Self::new(LogicalEncoding::none(kind), PhysicalEncoding::None)
410    }
411}
412
413/// Metadata about an encoded stream
414#[derive(Clone, Copy, Dbg, PartialEq)]
415pub struct StreamMeta {
416    #[dbg(formatter = "compact_dbg")]
417    pub stream_type: StreamType,
418    #[dbg(formatter = "compact_dbg")]
419    pub encoding: IntEncoding,
420    pub(crate) num_values: u32,
421}
422
423impl StreamMeta {
424    #[inline]
425    pub(crate) fn new(stream_type: StreamType, encoding: IntEncoding, num_values: u32) -> Self {
426        Self {
427            stream_type,
428            encoding,
429            num_values,
430        }
431    }
432
433    #[inline]
434    pub(crate) fn new2(
435        stream_type: StreamType,
436        logical: LogicalEncoding,
437        physical: PhysicalEncoding,
438        num_values: usize,
439    ) -> MltResult<Self> {
440        let enc = IntEncoding::new(logical, physical);
441        Ok(Self::new(stream_type, enc, u32::try_from(num_values)?))
442    }
443
444    #[inline]
445    pub(crate) fn new_none(
446        stream_type: StreamType,
447        kind: ValueKind,
448        num_values: usize,
449    ) -> MltResult<Self> {
450        let enc = IntEncoding::none(kind);
451        Ok(Self::new(stream_type, enc, u32::try_from(num_values)?))
452    }
453}
454
455/// Representation of an encoded stream
456#[derive(Clone, Dbg, PartialEq)]
457pub struct RawStream<'a> {
458    pub meta: StreamMeta,
459    #[dbg(formatter = "bytes_dbg")]
460    pub(crate) data: &'a [u8],
461}
462
463impl<'a> RawStream<'a> {
464    #[must_use]
465    pub(crate) fn new(meta: StreamMeta, data: &'a [u8]) -> Self {
466        Self { meta, data }
467    }
468}
469
470// Display impls the annotated dump's JSON carries instead of serde derives on this
471// vocabulary, so the wire enums do not become frozen public JSON API.
472
473impl Display for StreamType {
474    /// `present`, `data`, `data[fsst]`, `offset[string]`, `length[rings]`.
475    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
476        match self {
477            Self::Present => f.write_str("present"),
478            Self::Data(dict) => {
479                let name = match dict {
480                    DictionaryType::None => return f.write_str("data"),
481                    DictionaryType::Single => "single",
482                    DictionaryType::Shared => "shared",
483                    DictionaryType::Vertex => "vertex",
484                    DictionaryType::Morton => "morton",
485                    DictionaryType::Fsst => "fsst",
486                };
487                write!(f, "data[{name}]")
488            }
489            Self::Offset(offset) => {
490                let name = match offset {
491                    OffsetType::Vertex => "vertex",
492                    OffsetType::Index => "index",
493                    OffsetType::String => "string",
494                    OffsetType::Key => "key",
495                };
496                write!(f, "offset[{name}]")
497            }
498            Self::Length(length) => {
499                let name = match length {
500                    LengthType::VarBinary => "var-binary",
501                    LengthType::Geometries => "geometries",
502                    LengthType::Parts => "parts",
503                    LengthType::Rings => "rings",
504                    LengthType::Triangles => "triangles",
505                    LengthType::Symbol => "symbol",
506                    LengthType::Dictionary => "dictionary",
507                    #[cfg(feature = "unstable-v2")]
508                    LengthType::Nested => "nested",
509                };
510                write!(f, "length[{name}]")
511            }
512        }
513    }
514}
515
516impl Display for LogicalEncoding {
517    /// The value kind, then its encoding: `int/rle`, `float/alp`, `vertex/morton-delta`.
518    ///
519    /// Run parameters are left out, they belong to the region's own annotation.
520    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
521        let (kind, enc) = match self {
522            Self::Int(int) => (
523                "int",
524                match int {
525                    IntLogical::None => "none",
526                    IntLogical::Delta => "delta",
527                    IntLogical::Rle(_) => "rle",
528                    IntLogical::DeltaRle(_) => "delta-rle",
529                },
530            ),
531            Self::Bool(b) => (
532                "bool",
533                match b {
534                    BoolLogical::None => "none",
535                    BoolLogical::ByteRle(_) => "byte-rle",
536                },
537            ),
538            Self::Float(float) => (
539                "float",
540                match float {
541                    FloatLogical::None => "none",
542                    FloatLogical::Dict => "dict",
543                    FloatLogical::Alp(_) => "alp",
544                },
545            ),
546            Self::Vertex(vertex) => (
547                "vertex",
548                match vertex {
549                    VertexLogical::None => "none",
550                    VertexLogical::Delta => "delta",
551                    VertexLogical::ComponentwiseDelta => "componentwise-delta",
552                    VertexLogical::Morton(_) => "morton",
553                    VertexLogical::MortonDelta(_) => "morton-delta",
554                    VertexLogical::MortonRle(_) => "morton-rle",
555                },
556            ),
557        };
558        write!(f, "{kind}/{enc}")
559    }
560}
561
562impl Display for PhysicalEncoding {
563    /// `none`, `varint`, `fastpfor[256be]`, `bit-packed`.
564    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
565        match self {
566            Self::None => f.write_str("none"),
567            Self::VarInt => f.write_str("varint"),
568            Self::FastPFor(kind) => {
569                let name = match kind {
570                    FastPForKind::Block256Be => "256be",
571                    #[cfg(feature = "unstable-v2")]
572                    FastPForKind::Block128Le => "128le",
573                };
574                write!(f, "fastpfor[{name}]")
575            }
576            #[cfg(feature = "unstable-v2")]
577            Self::BitPacked => f.write_str("bit-packed"),
578        }
579    }
580}
581
582#[cfg(test)]
583mod tests {
584    use rstest::rstest;
585
586    use super::*;
587
588    fn morton() -> Morton {
589        Morton { bits: 4, shift: 0 }
590    }
591
592    fn alp() -> Alp {
593        Alp {
594            scale: AlpScale { e: 3, f: 1 },
595            base: -5,
596        }
597    }
598
599    fn rle() -> RleMeta {
600        RleMeta::Split {
601            runs: 2,
602            num_rle_values: 5,
603        }
604    }
605
606    #[rstest]
607    #[case::present(StreamType::Present, "present")]
608    #[case::data_none(StreamType::Data(DictionaryType::None), "data")]
609    #[case::data_single(StreamType::Data(DictionaryType::Single), "data[single]")]
610    #[case::data_shared(StreamType::Data(DictionaryType::Shared), "data[shared]")]
611    #[case::data_vertex(StreamType::Data(DictionaryType::Vertex), "data[vertex]")]
612    #[case::data_morton(StreamType::Data(DictionaryType::Morton), "data[morton]")]
613    #[case::data_fsst(StreamType::Data(DictionaryType::Fsst), "data[fsst]")]
614    #[case::offset_vertex(StreamType::Offset(OffsetType::Vertex), "offset[vertex]")]
615    #[case::offset_index(StreamType::Offset(OffsetType::Index), "offset[index]")]
616    #[case::offset_string(StreamType::Offset(OffsetType::String), "offset[string]")]
617    #[case::offset_key(StreamType::Offset(OffsetType::Key), "offset[key]")]
618    #[case::length_var_binary(StreamType::Length(LengthType::VarBinary), "length[var-binary]")]
619    #[case::length_geometries(StreamType::Length(LengthType::Geometries), "length[geometries]")]
620    #[case::length_parts(StreamType::Length(LengthType::Parts), "length[parts]")]
621    #[case::length_rings(StreamType::Length(LengthType::Rings), "length[rings]")]
622    #[case::length_triangles(StreamType::Length(LengthType::Triangles), "length[triangles]")]
623    #[case::length_symbol(StreamType::Length(LengthType::Symbol), "length[symbol]")]
624    #[case::length_dictionary(StreamType::Length(LengthType::Dictionary), "length[dictionary]")]
625    fn every_stream_type_renders_its_wire_label(
626        #[case] stream_type: StreamType,
627        #[case] expected: &str,
628    ) {
629        assert_eq!(stream_type.to_string(), expected);
630    }
631
632    #[cfg(feature = "unstable-v2")]
633    #[test]
634    fn a_nested_length_stream_renders_its_wire_label() {
635        assert_eq!(
636            StreamType::Length(LengthType::Nested).to_string(),
637            "length[nested]"
638        );
639    }
640
641    #[rstest]
642    #[case::int_none(LogicalEncoding::Int(IntLogical::None), "int/none")]
643    #[case::int_delta(LogicalEncoding::Int(IntLogical::Delta), "int/delta")]
644    #[case::int_rle(LogicalEncoding::Int(IntLogical::Rle(rle())), "int/rle")]
645    #[case::int_delta_rle(LogicalEncoding::Int(IntLogical::DeltaRle(rle())), "int/delta-rle")]
646    #[case::bool_none(LogicalEncoding::Bool(BoolLogical::None), "bool/none")]
647    #[case::bool_byte_rle(LogicalEncoding::Bool(BoolLogical::ByteRle(rle())), "bool/byte-rle")]
648    #[case::float_none(LogicalEncoding::Float(FloatLogical::None), "float/none")]
649    #[case::float_dict(LogicalEncoding::Float(FloatLogical::Dict), "float/dict")]
650    #[case::float_alp(LogicalEncoding::Float(FloatLogical::Alp(alp())), "float/alp")]
651    #[case::vertex_none(LogicalEncoding::Vertex(VertexLogical::None), "vertex/none")]
652    #[case::vertex_delta(LogicalEncoding::Vertex(VertexLogical::Delta), "vertex/delta")]
653    #[case::vertex_componentwise_delta(
654        LogicalEncoding::Vertex(VertexLogical::ComponentwiseDelta),
655        "vertex/componentwise-delta"
656    )]
657    #[case::vertex_morton(
658        LogicalEncoding::Vertex(VertexLogical::Morton(morton())),
659        "vertex/morton"
660    )]
661    #[case::vertex_morton_delta(
662        LogicalEncoding::Vertex(VertexLogical::MortonDelta(morton())),
663        "vertex/morton-delta"
664    )]
665    #[case::vertex_morton_rle(
666        LogicalEncoding::Vertex(VertexLogical::MortonRle(morton())),
667        "vertex/morton-rle"
668    )]
669    fn every_logical_encoding_renders_kind_then_encoding(
670        #[case] encoding: LogicalEncoding,
671        #[case] expected: &str,
672    ) {
673        assert_eq!(encoding.to_string(), expected);
674    }
675
676    #[rstest]
677    #[case::none(PhysicalEncoding::None, "none")]
678    #[case::varint(PhysicalEncoding::VarInt, "varint")]
679    #[case::fastpfor_256be(
680        PhysicalEncoding::FastPFor(FastPForKind::Block256Be),
681        "fastpfor[256be]"
682    )]
683    fn every_physical_encoding_renders_its_wire_label(
684        #[case] encoding: PhysicalEncoding,
685        #[case] expected: &str,
686    ) {
687        assert_eq!(encoding.to_string(), expected);
688    }
689
690    #[cfg(feature = "unstable-v2")]
691    #[rstest]
692    #[case::fastpfor_128le(
693        PhysicalEncoding::FastPFor(FastPForKind::Block128Le),
694        "fastpfor[128le]"
695    )]
696    #[case::bit_packed(PhysicalEncoding::BitPacked, "bit-packed")]
697    fn every_v2_physical_encoding_renders_its_wire_label(
698        #[case] encoding: PhysicalEncoding,
699        #[case] expected: &str,
700    ) {
701        assert_eq!(encoding.to_string(), expected);
702    }
703
704    #[rstest]
705    #[case::int(LogicalEncoding::Int(IntLogical::None), ValueKind::Int)]
706    #[case::bool(LogicalEncoding::Bool(BoolLogical::None), ValueKind::Bool)]
707    #[case::float(LogicalEncoding::Float(FloatLogical::None), ValueKind::Float)]
708    #[case::vertex(LogicalEncoding::Vertex(VertexLogical::None), ValueKind::Vertex)]
709    fn the_identity_encoding_of_a_kind_reports_that_kind_back(
710        #[case] expected: LogicalEncoding,
711        #[case] kind: ValueKind,
712    ) {
713        let none = LogicalEncoding::none(kind);
714        assert_eq!(none, expected);
715        assert_eq!(none.kind(), kind);
716        assert!(none.is_identity());
717    }
718}