Skip to main content

zeppelin_embed/segment/
layout.rs

1//! Permanent little-endian segment layout declarations and region codecs.
2
3use crate::format::{FormatFamily, FormatRegistry};
4use crate::meta::{
5    AliveSet, Column, ColumnDefinition, ColumnId, ColumnInput, ColumnStore, ColumnStoreBuilder,
6    ColumnType, ColumnValue, Schema, TIMESTAMP_COLUMN,
7};
8use crate::quant::Bit4Factors;
9
10use super::{MetadataDecodeProvenance, SegmentError};
11
12/// Darwin-friendly region alignment; no row is padded.
13pub const REGION_ALIGNMENT: usize = 16 * 1024;
14/// Per-chunk checksum granularity.
15pub const CHECKSUM_CHUNK_BYTES: usize = 64 * 1024;
16/// Segment-specific bytes following the common file header.
17pub const SEGMENT_PREFIX_LEN: usize = 32;
18/// Permanent region-directory entry width.
19pub const REGION_ENTRY_LEN: usize = 32;
20/// Permanent vector-region geometry header width.
21pub const VECTOR_HEADER_LEN: usize = 32;
22
23/// Known and reserved region identifiers.
24#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
25#[repr(u16)]
26pub enum RegionKind {
27    /// Typed metadata arrays.
28    Columns = 1,
29    /// Alive bitmap and tombstone mask.
30    Alive = 2,
31    /// Structure-of-arrays packed vector codes.
32    VectorCodes = 3,
33    /// Structure-of-arrays factor records.
34    VectorFactors = 4,
35    /// Structure-of-arrays exact-rescore rows.
36    VectorRescore = 5,
37    /// Reserved for Task 13 postings.
38    Postings = 6,
39    /// Fixed-stride graph node blocks; originally reserved under the obsolete CSR name.
40    GraphNodeBlocks = 7,
41    /// Reserved for graph-neighbor-colocated codes.
42    GraphColocatedCodes = 8,
43    /// Reserved for a future sign-plane payload.
44    SignPlane = 9,
45    /// Reserved for optional clustered PDX blocks.
46    PdxClusteredBlocks = 10,
47    /// Per-64-KB xxh3 checksum table.
48    ChecksumTable = 11,
49    /// Dense `(doc_id:u128, revision:u64)` records for sealed store rows.
50    DocumentVersions = 12,
51    /// Dense length-delimited opaque stored metadata rows.
52    StoredMetadata = 13,
53    /// Dense optional UTF-8 source text rows.
54    StoredText = 14,
55    /// First id reserved for additive vector-space-N region triples.
56    VectorSpaceN = 4096,
57}
58
59impl RegionKind {
60    /// Returns the permanent numeric identifier.
61    #[must_use]
62    pub const fn id(self) -> u16 {
63        self as u16
64    }
65
66    /// Resolves a known or reserved identifier; unknown values remain skippable.
67    #[must_use]
68    pub const fn from_id(id: u16) -> Option<Self> {
69        match id {
70            1 => Some(Self::Columns),
71            2 => Some(Self::Alive),
72            3 => Some(Self::VectorCodes),
73            4 => Some(Self::VectorFactors),
74            5 => Some(Self::VectorRescore),
75            6 => Some(Self::Postings),
76            7 => Some(Self::GraphNodeBlocks),
77            8 => Some(Self::GraphColocatedCodes),
78            9 => Some(Self::SignPlane),
79            10 => Some(Self::PdxClusteredBlocks),
80            11 => Some(Self::ChecksumTable),
81            12 => Some(Self::DocumentVersions),
82            13 => Some(Self::StoredMetadata),
83            14 => Some(Self::StoredText),
84            4096 => Some(Self::VectorSpaceN),
85            _ => None,
86        }
87    }
88
89    pub(crate) const fn family(self) -> Option<FormatFamily> {
90        match self {
91            Self::Columns => Some(FormatFamily::Columns),
92            Self::Alive => Some(FormatFamily::Alive),
93            Self::VectorCodes => Some(FormatFamily::VectorCodes),
94            Self::VectorFactors => Some(FormatFamily::VectorFactors),
95            Self::VectorRescore => Some(FormatFamily::VectorRescore),
96            Self::Postings => Some(FormatFamily::Postings),
97            Self::GraphNodeBlocks => Some(FormatFamily::GraphNodeBlocks),
98            Self::DocumentVersions => Some(FormatFamily::DocumentVersions),
99            Self::StoredMetadata => Some(FormatFamily::StoredMetadata),
100            Self::StoredText => Some(FormatFamily::StoredText),
101            Self::ChecksumTable => Some(FormatFamily::ChecksumTable),
102            Self::GraphColocatedCodes
103            | Self::SignPlane
104            | Self::PdxClusteredBlocks
105            | Self::VectorSpaceN => None,
106        }
107    }
108}
109
110/// One fixed-width region-directory entry.
111#[derive(Clone, Copy, Debug, Eq, PartialEq)]
112#[repr(C)]
113pub struct RegionEntry {
114    /// Known or forward-compatible region kind id.
115    pub kind: u16,
116    /// Additive region-family version.
117    pub version: u16,
118    /// Explicit zero padding reserved for future flags.
119    pub reserved: u32,
120    /// Absolute aligned file offset.
121    pub offset: u64,
122    /// Exact byte length.
123    pub length: u64,
124    /// Whole-region xxh3-64 checksum.
125    pub checksum: u64,
126}
127
128/// Permanent vector geometry and transform descriptor.
129#[derive(Clone, Copy, Debug, Eq, PartialEq)]
130#[repr(C)]
131pub struct VectorHeader {
132    /// Per-segment quantization or rescore scheme id.
133    pub scheme: u16,
134    /// Vector-space identifier; v1 requires zero.
135    pub vector_space_id: u16,
136    /// Logical dimension.
137    pub dims: u32,
138    /// Exact bytes between adjacent code/rescore rows.
139    pub row_stride_bytes: u32,
140    /// Exact bytes between factor records.
141    pub factor_stride_bytes: u16,
142    /// Transform kind; v1 requires identity zero.
143    pub transform_kind: u16,
144    /// Transform seed; v1 requires zero.
145    pub transform_seed: u64,
146    /// Dense segment-local row count.
147    pub row_count: u32,
148    /// Explicit must-be-zero padding.
149    pub reserved: u32,
150}
151
152impl VectorHeader {
153    /// Validates all v1 invariants before any payload cast.
154    pub fn validate(self) -> Result<(), SegmentError> {
155        FormatRegistry::require_scheme(self.scheme)
156            .map_err(|error| SegmentError::Geometry(error.to_string()))?;
157        if self.vector_space_id != 0 {
158            return Err(SegmentError::Geometry(format!(
159                "v1 vector_space_id must be 0, got {}",
160                self.vector_space_id
161            )));
162        }
163        if self.transform_kind != 0 || self.transform_seed != 0 {
164            return Err(SegmentError::Geometry(format!(
165                "v1 transform must be identity/0, got kind {} seed {}",
166                self.transform_kind, self.transform_seed
167            )));
168        }
169        if self.reserved != 0 {
170            return Err(SegmentError::Geometry(format!(
171                "vector header reserved field must be 0, got {}",
172                self.reserved
173            )));
174        }
175        Ok(())
176    }
177}
178
179/// Permanent eight-byte affine Int8 factor record.
180#[derive(Clone, Copy, Debug, PartialEq)]
181#[repr(C)]
182pub struct Int8Factors {
183    /// Reconstruction step.
184    pub scale: f32,
185    /// Reconstructed value at code zero.
186    pub offset: f32,
187}
188
189const _: [(); 12] = [(); std::mem::size_of::<Bit4Factors>()];
190const _: [(); 8] = [(); std::mem::size_of::<Int8Factors>()];
191const _: [(); REGION_ENTRY_LEN] = [(); std::mem::size_of::<RegionEntry>()];
192const _: [(); VECTOR_HEADER_LEN] = [(); std::mem::size_of::<VectorHeader>()];
193
194pub(crate) fn align_up(value: usize, alignment: usize) -> Result<usize, SegmentError> {
195    let adjustment = alignment.saturating_sub(1);
196    value
197        .checked_add(adjustment)
198        .map(|rounded| rounded / alignment * alignment)
199        .ok_or_else(|| SegmentError::Geometry("aligned offset overflow".to_owned()))
200}
201
202pub(crate) fn encode_vector_header(header: VectorHeader, output: &mut Vec<u8>) {
203    output.extend_from_slice(&header.scheme.to_le_bytes());
204    output.extend_from_slice(&header.vector_space_id.to_le_bytes());
205    output.extend_from_slice(&header.dims.to_le_bytes());
206    output.extend_from_slice(&header.row_stride_bytes.to_le_bytes());
207    output.extend_from_slice(&header.factor_stride_bytes.to_le_bytes());
208    output.extend_from_slice(&header.transform_kind.to_le_bytes());
209    output.extend_from_slice(&header.transform_seed.to_le_bytes());
210    output.extend_from_slice(&header.row_count.to_le_bytes());
211    output.extend_from_slice(&header.reserved.to_le_bytes());
212}
213
214pub(crate) fn decode_vector_header(bytes: &[u8]) -> Result<VectorHeader, SegmentError> {
215    let mut cursor = Cursor::new("vector-header", bytes);
216    let header = VectorHeader {
217        scheme: cursor.u16()?,
218        vector_space_id: cursor.u16()?,
219        dims: cursor.u32()?,
220        row_stride_bytes: cursor.u32()?,
221        factor_stride_bytes: cursor.u16()?,
222        transform_kind: cursor.u16()?,
223        transform_seed: cursor.u64()?,
224        row_count: cursor.u32()?,
225        reserved: cursor.u32()?,
226    };
227    header.validate()?;
228    Ok(header)
229}
230
231pub(crate) fn encode_alive(alive: &AliveSet) -> Vec<u8> {
232    let mut output = Vec::new();
233    output.extend_from_slice(&alive.row_count().to_le_bytes());
234    let byte_len = usize::try_from(alive.row_count())
235        .unwrap_or(usize::MAX)
236        .div_ceil(8);
237    let mut bits = vec![0_u8; byte_len];
238    for row in alive.iter_alive() {
239        let position = row as usize;
240        let byte = position / 8;
241        let bit = position % 8;
242        if let Some(value) = bits.get_mut(byte) {
243            *value |= 1_u8 << bit;
244        }
245    }
246    output.extend_from_slice(&(bits.len() as u32).to_le_bytes());
247    output.extend_from_slice(&bits);
248    output
249}
250
251pub(crate) fn decode_alive(bytes: &[u8]) -> Result<AliveSet, SegmentError> {
252    let mut cursor = Cursor::new("alive", bytes);
253    let row_count = cursor.u32().map_err(alive_cursor_error)?;
254    let declared_bytes = cursor.u32().map_err(alive_cursor_error)?;
255    let byte_len = usize::try_from(declared_bytes)
256        .map_err(|_| SegmentError::Alive("bitmap length exceeds usize".to_owned()))?;
257    let expected = (row_count as usize).div_ceil(8);
258    if byte_len != expected {
259        return Err(SegmentError::Alive(format!(
260            "bitmap length {byte_len}, expected {expected}"
261        )));
262    }
263    let byte_offset = cursor.position_u64().map_err(alive_cursor_error)?;
264    if cursor.remaining() < byte_len {
265        let observed_bytes = u32::try_from(cursor.remaining()).map_err(|_| {
266            SegmentError::Alive("truncated bitmap remainder exceeds u32".to_owned())
267        })?;
268        return Err(SegmentError::MetadataSemantic {
269            detail: format!(
270                "alive truncated at {}, need {byte_len}, total {}",
271                cursor.position,
272                bytes.len()
273            ),
274            provenance: MetadataDecodeProvenance::AliveBitmapTruncation {
275                row_count,
276                byte_offset,
277                declared_bytes,
278                observed_bytes,
279            },
280        });
281    }
282    let bits = cursor.take(byte_len).map_err(alive_cursor_error)?;
283    cursor.finish().map_err(SegmentError::Alive)?;
284    if !row_count.is_multiple_of(8) {
285        let used = row_count % 8;
286        if let Some(last) = bits.last() {
287            let mask = !((1_u8 << used) - 1);
288            if last & mask != 0 {
289                return Err(SegmentError::Alive(
290                    "non-zero bitmap tail padding".to_owned(),
291                ));
292            }
293        }
294    }
295    let mut alive = AliveSet::new(row_count);
296    for row in 0..row_count {
297        let position = row as usize;
298        let is_alive = bits
299            .get(position / 8)
300            .is_some_and(|value| value & (1_u8 << (position % 8)) != 0);
301        if !is_alive {
302            alive
303                .tombstone(row)
304                .map_err(|error| SegmentError::Alive(error.to_string()))?;
305        }
306    }
307    Ok(alive)
308}
309
310fn alive_cursor_error(error: SegmentError) -> SegmentError {
311    match error {
312        SegmentError::Columns(detail) => SegmentError::Alive(detail),
313        other => other,
314    }
315}
316
317fn column_type_id(column_type: ColumnType) -> u16 {
318    match column_type {
319        ColumnType::U64 => 1,
320        ColumnType::I64 => 2,
321        ColumnType::F64 => 3,
322        ColumnType::Bool => 4,
323        ColumnType::DictionaryString => 5,
324        ColumnType::RawString => 6,
325    }
326}
327
328fn column_type_from_id(id: u16) -> Result<ColumnType, SegmentError> {
329    match id {
330        1 => Ok(ColumnType::U64),
331        2 => Ok(ColumnType::I64),
332        3 => Ok(ColumnType::F64),
333        4 => Ok(ColumnType::Bool),
334        5 => Ok(ColumnType::DictionaryString),
335        6 => Ok(ColumnType::RawString),
336        _ => Err(SegmentError::Columns(format!("unknown column type {id}"))),
337    }
338}
339
340pub(crate) fn encode_columns(store: &ColumnStore) -> Result<Vec<u8>, SegmentError> {
341    let mut output = Vec::new();
342    output.extend_from_slice(&store.row_count().to_le_bytes());
343    output.extend_from_slice(&(store.schema().columns().len() as u32).to_le_bytes());
344    for definition in store.schema().columns() {
345        output.extend_from_slice(&definition.id().get().to_le_bytes());
346        output.extend_from_slice(&column_type_id(definition.column_type()).to_le_bytes());
347        output.extend_from_slice(&u16::from(definition.is_nullable()).to_le_bytes());
348        let name = definition.name().as_bytes();
349        let name_length = u32::try_from(name.len())
350            .map_err(|_| SegmentError::Columns("column name exceeds u32".to_owned()))?;
351        output.extend_from_slice(&name_length.to_le_bytes());
352        output.extend_from_slice(name);
353    }
354    for definition in store.schema().columns() {
355        let column = store.column(definition.id()).ok_or_else(|| {
356            SegmentError::Columns(format!("missing column {}", definition.id().get()))
357        })?;
358        let presence = encode_presence(column, store.row_count());
359        output.extend_from_slice(&(presence.len() as u32).to_le_bytes());
360        output.extend_from_slice(&presence);
361        encode_column_values(column, store.row_count(), &mut output)?;
362    }
363    Ok(output)
364}
365
366fn encode_presence(column: &Column, row_count: u32) -> Vec<u8> {
367    let mut bytes = vec![0_u8; (row_count as usize).div_ceil(8)];
368    for row in column.present().iter() {
369        let position = row as usize;
370        if let Some(value) = bytes.get_mut(position / 8) {
371            *value |= 1_u8 << (position % 8);
372        }
373    }
374    bytes
375}
376
377fn encode_column_values(
378    column: &Column,
379    row_count: u32,
380    output: &mut Vec<u8>,
381) -> Result<(), SegmentError> {
382    match column {
383        Column::U64(values) => {
384            for row in 0..row_count {
385                output.extend_from_slice(&values.get(row).unwrap_or(0).to_le_bytes());
386            }
387        }
388        Column::I64(values) => {
389            for row in 0..row_count {
390                output.extend_from_slice(&values.get(row).unwrap_or(0).to_le_bytes());
391            }
392        }
393        Column::F64(values) => {
394            for row in 0..row_count {
395                output.extend_from_slice(&values.get(row).unwrap_or(0.0).to_bits().to_le_bytes());
396            }
397        }
398        Column::Bool(values) => {
399            for row in 0..row_count {
400                output.push(u8::from(values.get(row).unwrap_or(false)));
401            }
402        }
403        Column::DictionaryString(values) => {
404            let dictionary_length = u32::try_from(values.dictionary().len())
405                .map_err(|_| SegmentError::Columns("dictionary exceeds u32".to_owned()))?;
406            output.extend_from_slice(&dictionary_length.to_le_bytes());
407            for code in 0..dictionary_length {
408                let value = values.dictionary().get(code).ok_or_else(|| {
409                    SegmentError::Columns(format!("missing dictionary code {code}"))
410                })?;
411                encode_string(value, output)?;
412            }
413            output.extend_from_slice(
414                &match values.codes().width() {
415                    crate::meta::CodeWidth::U16 => 2_u16,
416                    crate::meta::CodeWidth::U32 => 4_u16,
417                }
418                .to_le_bytes(),
419            );
420            output.extend_from_slice(&0_u16.to_le_bytes());
421            for row in 0..row_count {
422                let code = values.codes().get(row as usize).unwrap_or(0);
423                match values.codes().width() {
424                    crate::meta::CodeWidth::U16 => {
425                        output.extend_from_slice(&(code as u16).to_le_bytes())
426                    }
427                    crate::meta::CodeWidth::U32 => output.extend_from_slice(&code.to_le_bytes()),
428                }
429            }
430        }
431        Column::RawString(values) => {
432            for row in 0..row_count {
433                encode_string(values.get(row).unwrap_or(""), output)?;
434            }
435        }
436    }
437    Ok(())
438}
439
440fn encode_string(value: &str, output: &mut Vec<u8>) -> Result<(), SegmentError> {
441    let length = u32::try_from(value.len())
442        .map_err(|_| SegmentError::Columns("string exceeds u32".to_owned()))?;
443    output.extend_from_slice(&length.to_le_bytes());
444    output.extend_from_slice(value.as_bytes());
445    Ok(())
446}
447
448enum DecodedValues {
449    U64(Vec<u64>),
450    I64(Vec<i64>),
451    F64(Vec<f64>),
452    Bool(Vec<bool>),
453    String(Vec<String>),
454}
455
456struct DecodedColumn {
457    definition: ColumnDefinition,
458    present: Vec<u8>,
459    values: DecodedValues,
460}
461
462pub(crate) fn decode_columns(bytes: &[u8]) -> Result<ColumnStore, SegmentError> {
463    let mut cursor = Cursor::new("columns", bytes);
464    let row_count = cursor.u32()?;
465    let column_count = cursor.usize_from_u32()?;
466    if column_count == 0 {
467        return Err(SegmentError::Columns(
468            "required timestamp column is absent".to_owned(),
469        ));
470    }
471    let mut definitions = Vec::with_capacity(column_count);
472    for _ in 0..column_count {
473        let id = ColumnId::new(cursor.u32()?);
474        let column_type = column_type_from_id(cursor.u16()?)?;
475        let nullable = match cursor.u16()? {
476            0 => false,
477            1 => true,
478            value => {
479                return Err(SegmentError::Columns(format!(
480                    "invalid nullable flag {value}"
481                )));
482            }
483        };
484        let name_length = cursor.usize_from_u32()?;
485        let name = std::str::from_utf8(cursor.take(name_length)?)
486            .map_err(|error| SegmentError::Columns(format!("column name UTF-8: {error}")))?
487            .to_owned();
488        definitions.push(ColumnDefinition::new(id, name, column_type, nullable));
489    }
490    let timestamp = definitions.first().ok_or_else(|| {
491        SegmentError::Columns("required timestamp definition is absent".to_owned())
492    })?;
493    if timestamp.id() != TIMESTAMP_COLUMN
494        || timestamp.name() != "ts"
495        || timestamp.column_type() != ColumnType::I64
496        || timestamp.is_nullable()
497    {
498        return Err(SegmentError::Columns(
499            "timestamp definition is not canonical".to_owned(),
500        ));
501    }
502
503    let mut decoded = Vec::with_capacity(column_count);
504    for definition in definitions {
505        let presence_length = cursor.usize_from_u32()?;
506        let expected_presence = (row_count as usize).div_ceil(8);
507        if presence_length != expected_presence {
508            return Err(SegmentError::Columns(format!(
509                "column {} presence length {presence_length}, expected {expected_presence}",
510                definition.id().get()
511            )));
512        }
513        let presence_offset = cursor.position_u64()?;
514        let present = cursor.take(presence_length)?.to_vec();
515        validate_presence_tail(definition.id(), row_count, presence_offset, &present)?;
516        let values = decode_column_values(
517            definition.id(),
518            definition.column_type(),
519            row_count,
520            &present,
521            &mut cursor,
522        )?;
523        decoded.push(DecodedColumn {
524            definition,
525            present,
526            values,
527        });
528    }
529    cursor.finish().map_err(SegmentError::Columns)?;
530
531    let user_definitions = decoded
532        .iter()
533        .skip(1)
534        .map(|column| column.definition.clone())
535        .collect();
536    let schema =
537        Schema::new(user_definitions).map_err(|error| SegmentError::Columns(error.to_string()))?;
538    let mut builder = ColumnStoreBuilder::new(schema);
539    for row in 0..row_count {
540        let timestamp = decoded
541            .first()
542            .and_then(|column| match &column.values {
543                DecodedValues::I64(values) => values.get(row as usize).copied(),
544                _ => None,
545            })
546            .ok_or_else(|| SegmentError::Columns(format!("missing timestamp row {row}")))?;
547        let mut inputs = Vec::new();
548        for column in decoded.iter().skip(1) {
549            if !presence_contains(&column.present, row) {
550                continue;
551            }
552            let value = decoded_value(&column.values, row)?;
553            inputs.push(ColumnInput {
554                column: column.definition.id(),
555                value,
556            });
557        }
558        builder
559            .push_row(timestamp, &inputs)
560            .map_err(|error| SegmentError::Columns(error.to_string()))?;
561    }
562    builder
563        .finish()
564        .map_err(|error| SegmentError::Columns(error.to_string()))
565}
566
567fn decode_column_values(
568    column_id: ColumnId,
569    column_type: ColumnType,
570    row_count: u32,
571    present: &[u8],
572    cursor: &mut Cursor<'_>,
573) -> Result<DecodedValues, SegmentError> {
574    match column_type {
575        ColumnType::U64 => (0..row_count)
576            .map(|_| cursor.u64())
577            .collect::<Result<Vec<_>, _>>()
578            .map(DecodedValues::U64),
579        ColumnType::I64 => (0..row_count)
580            .map(|_| cursor.i64())
581            .collect::<Result<Vec<_>, _>>()
582            .map(DecodedValues::I64),
583        ColumnType::F64 => (0..row_count)
584            .map(|_| cursor.u64().map(f64::from_bits))
585            .collect::<Result<Vec<_>, _>>()
586            .map(DecodedValues::F64),
587        ColumnType::Bool => (0..row_count)
588            .map(|_| match cursor.u8()? {
589                0 => Ok(false),
590                1 => Ok(true),
591                value => Err(SegmentError::Columns(format!(
592                    "invalid Boolean byte {value}"
593                ))),
594            })
595            .collect::<Result<Vec<_>, _>>()
596            .map(DecodedValues::Bool),
597        ColumnType::DictionaryString => {
598            let dictionary_cardinality = cursor.u32()?;
599            let dictionary_length = usize::try_from(dictionary_cardinality).map_err(|_| {
600                SegmentError::Columns("dictionary cardinality exceeds usize".to_owned())
601            })?;
602            let remaining_entry_capacity = cursor.remaining() / std::mem::size_of::<u32>();
603            if cursor.remaining() >= std::mem::size_of::<u32>()
604                && dictionary_length > remaining_entry_capacity
605            {
606                return Err(SegmentError::Columns(format!(
607                    "dictionary cardinality exceeds remaining entry capacity: declared {dictionary_cardinality}, capacity {remaining_entry_capacity}"
608                )));
609            }
610            let mut dictionary = Vec::with_capacity(dictionary_length);
611            for entry in 0..dictionary_length {
612                let declared_bytes = cursor.u32()?;
613                let length = usize::try_from(declared_bytes).map_err(|_| {
614                    SegmentError::Columns(
615                        "dictionary string storage length exceeds usize".to_owned(),
616                    )
617                })?;
618                if cursor.remaining() < length && length > cursor.bytes.len() {
619                    return Err(SegmentError::Columns(format!(
620                        "dictionary string storage exceeds remaining Columns bytes at entry {entry}: declared {declared_bytes}, available {}",
621                        cursor.remaining()
622                    )));
623                }
624                let value = std::str::from_utf8(cursor.take(length)?)
625                    .map(str::to_owned)
626                    .map_err(|error| SegmentError::Columns(format!("columns UTF-8: {error}")))?;
627                dictionary.push(value);
628            }
629            let width = cursor.u16()?;
630            if cursor.u16()? != 0 {
631                return Err(SegmentError::Columns(
632                    "dictionary reserved field is non-zero".to_owned(),
633                ));
634            }
635            let mut values = Vec::with_capacity(row_count as usize);
636            for row in 0..row_count {
637                let byte_offset = cursor.position_u64()?;
638                let code = match width {
639                    2 => u32::from(cursor.u16()?),
640                    4 => cursor.u32()?,
641                    _ => {
642                        return Err(SegmentError::Columns(format!(
643                            "invalid dictionary width {width}"
644                        )));
645                    }
646                };
647                if presence_contains(present, row) {
648                    let value = dictionary.get(code as usize).ok_or_else(|| {
649                        SegmentError::MetadataSemantic {
650                            detail: format!("dictionary code {code} out of range"),
651                            provenance: MetadataDecodeProvenance::ColumnsDictionaryCode {
652                                column_id: column_id.get(),
653                                row,
654                                byte_offset,
655                                code,
656                                dictionary_cardinality,
657                            },
658                        }
659                    })?;
660                    values.push(value.clone());
661                } else {
662                    if code != 0 {
663                        return Err(SegmentError::Columns(format!(
664                            "dictionary null row {row} has non-zero code {code}"
665                        )));
666                    }
667                    values.push(String::new());
668                }
669            }
670            Ok(DecodedValues::String(values))
671        }
672        ColumnType::RawString => (0..row_count)
673            .map(|row| decode_raw_string(column_id, row, cursor))
674            .collect::<Result<Vec<_>, _>>()
675            .map(DecodedValues::String),
676    }
677}
678
679fn decoded_value(values: &DecodedValues, row: u32) -> Result<ColumnValue<'_>, SegmentError> {
680    let position = row as usize;
681    match values {
682        DecodedValues::U64(values) => values.get(position).copied().map(ColumnValue::U64),
683        DecodedValues::I64(values) => values.get(position).copied().map(ColumnValue::I64),
684        DecodedValues::F64(values) => values.get(position).copied().map(ColumnValue::F64),
685        DecodedValues::Bool(values) => values.get(position).copied().map(ColumnValue::Bool),
686        DecodedValues::String(values) => {
687            values.get(position).map(|value| ColumnValue::String(value))
688        }
689    }
690    .ok_or_else(|| SegmentError::Columns(format!("missing row {row}")))
691}
692
693fn decode_raw_string(
694    column_id: ColumnId,
695    row: u32,
696    cursor: &mut Cursor<'_>,
697) -> Result<String, SegmentError> {
698    let byte_offset = cursor.position_u64()?;
699    let declared_bytes = cursor.u32()?;
700    let length = usize::try_from(declared_bytes)
701        .map_err(|_| SegmentError::Columns("raw string length exceeds usize".to_owned()))?;
702    if cursor.remaining() < length {
703        let available_bytes = u64::try_from(cursor.remaining())
704            .map_err(|_| SegmentError::Columns("raw string remainder exceeds u64".to_owned()))?;
705        return Err(SegmentError::MetadataSemantic {
706            detail: format!(
707                "columns truncated at {}, need {length}, total {}",
708                cursor.position,
709                cursor.bytes.len()
710            ),
711            provenance: MetadataDecodeProvenance::ColumnsRawStringLength {
712                column_id: column_id.get(),
713                row,
714                byte_offset,
715                declared_bytes,
716                available_bytes,
717            },
718        });
719    }
720    std::str::from_utf8(cursor.take(length)?)
721        .map(str::to_owned)
722        .map_err(|error| SegmentError::Columns(format!("columns UTF-8: {error}")))
723}
724
725fn validate_presence_tail(
726    column_id: ColumnId,
727    row_count: u32,
728    byte_offset: u64,
729    bytes: &[u8],
730) -> Result<(), SegmentError> {
731    if !row_count.is_multiple_of(8) {
732        let used = row_count % 8;
733        let allowed_mask = (1_u8 << used) - 1;
734        if let Some(observed_byte) = bytes.last().copied()
735            && observed_byte & !allowed_mask != 0
736        {
737            let last_offset = u64::try_from(bytes.len().saturating_sub(1))
738                .ok()
739                .and_then(|delta| byte_offset.checked_add(delta))
740                .ok_or_else(|| SegmentError::Columns("presence offset overflow".to_owned()))?;
741            return Err(SegmentError::MetadataSemantic {
742                detail: "non-zero presence tail padding".to_owned(),
743                provenance: MetadataDecodeProvenance::ColumnsPresenceTail {
744                    column_id: column_id.get(),
745                    row_count,
746                    byte_offset: last_offset,
747                    observed_byte,
748                    allowed_mask,
749                },
750            });
751        }
752    }
753    Ok(())
754}
755
756fn presence_contains(bytes: &[u8], row: u32) -> bool {
757    let position = row as usize;
758    bytes
759        .get(position / 8)
760        .is_some_and(|byte| byte & (1_u8 << (position % 8)) != 0)
761}
762
763pub(crate) struct Cursor<'a> {
764    artifact: &'static str,
765    bytes: &'a [u8],
766    position: usize,
767}
768
769impl<'a> Cursor<'a> {
770    pub(crate) const fn new(artifact: &'static str, bytes: &'a [u8]) -> Self {
771        Self {
772            artifact,
773            bytes,
774            position: 0,
775        }
776    }
777
778    pub(crate) fn take(&mut self, length: usize) -> Result<&'a [u8], SegmentError> {
779        let end = self
780            .position
781            .checked_add(length)
782            .ok_or_else(|| SegmentError::Columns(format!("{} offset overflow", self.artifact)))?;
783        let value = self.bytes.get(self.position..end).ok_or_else(|| {
784            SegmentError::Columns(format!(
785                "{} truncated at {}, need {length}, total {}",
786                self.artifact,
787                self.position,
788                self.bytes.len()
789            ))
790        })?;
791        self.position = end;
792        Ok(value)
793    }
794
795    pub(crate) fn position_u64(&self) -> Result<u64, SegmentError> {
796        u64::try_from(self.position)
797            .map_err(|_| SegmentError::Columns(format!("{} offset exceeds u64", self.artifact)))
798    }
799
800    pub(crate) fn remaining(&self) -> usize {
801        self.bytes.len().saturating_sub(self.position)
802    }
803
804    pub(crate) fn u8(&mut self) -> Result<u8, SegmentError> {
805        self.take(1)?
806            .first()
807            .copied()
808            .ok_or_else(|| SegmentError::Columns(format!("{} missing u8", self.artifact)))
809    }
810
811    pub(crate) fn u16(&mut self) -> Result<u16, SegmentError> {
812        let raw: [u8; 2] = self
813            .take(2)?
814            .try_into()
815            .map_err(|_| SegmentError::Columns(format!("{} invalid u16", self.artifact)))?;
816        Ok(u16::from_le_bytes(raw))
817    }
818
819    pub(crate) fn u32(&mut self) -> Result<u32, SegmentError> {
820        let raw: [u8; 4] = self
821            .take(4)?
822            .try_into()
823            .map_err(|_| SegmentError::Columns(format!("{} invalid u32", self.artifact)))?;
824        Ok(u32::from_le_bytes(raw))
825    }
826
827    pub(crate) fn u64(&mut self) -> Result<u64, SegmentError> {
828        let raw: [u8; 8] = self
829            .take(8)?
830            .try_into()
831            .map_err(|_| SegmentError::Columns(format!("{} invalid u64", self.artifact)))?;
832        Ok(u64::from_le_bytes(raw))
833    }
834
835    pub(crate) fn i64(&mut self) -> Result<i64, SegmentError> {
836        let raw: [u8; 8] = self
837            .take(8)?
838            .try_into()
839            .map_err(|_| SegmentError::Columns(format!("{} invalid i64", self.artifact)))?;
840        Ok(i64::from_le_bytes(raw))
841    }
842
843    pub(crate) fn usize_from_u32(&mut self) -> Result<usize, SegmentError> {
844        usize::try_from(self.u32()?)
845            .map_err(|_| SegmentError::Columns(format!("{} u32 exceeds usize", self.artifact)))
846    }
847
848    pub(crate) fn finish(&self) -> Result<(), String> {
849        if self.position == self.bytes.len() {
850            Ok(())
851        } else {
852            Err(format!(
853                "{} has {} trailing bytes",
854                self.artifact,
855                self.bytes.len().saturating_sub(self.position)
856            ))
857        }
858    }
859}