1use 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
12pub const REGION_ALIGNMENT: usize = 16 * 1024;
14pub const CHECKSUM_CHUNK_BYTES: usize = 64 * 1024;
16pub const SEGMENT_PREFIX_LEN: usize = 32;
18pub const REGION_ENTRY_LEN: usize = 32;
20pub const VECTOR_HEADER_LEN: usize = 32;
22
23#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
25#[repr(u16)]
26pub enum RegionKind {
27 Columns = 1,
29 Alive = 2,
31 VectorCodes = 3,
33 VectorFactors = 4,
35 VectorRescore = 5,
37 Postings = 6,
39 GraphNodeBlocks = 7,
41 GraphColocatedCodes = 8,
43 SignPlane = 9,
45 PdxClusteredBlocks = 10,
47 ChecksumTable = 11,
49 DocumentVersions = 12,
51 StoredMetadata = 13,
53 StoredText = 14,
55 VectorSpaceN = 4096,
57}
58
59impl RegionKind {
60 #[must_use]
62 pub const fn id(self) -> u16 {
63 self as u16
64 }
65
66 #[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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
112#[repr(C)]
113pub struct RegionEntry {
114 pub kind: u16,
116 pub version: u16,
118 pub reserved: u32,
120 pub offset: u64,
122 pub length: u64,
124 pub checksum: u64,
126}
127
128#[derive(Clone, Copy, Debug, Eq, PartialEq)]
130#[repr(C)]
131pub struct VectorHeader {
132 pub scheme: u16,
134 pub vector_space_id: u16,
136 pub dims: u32,
138 pub row_stride_bytes: u32,
140 pub factor_stride_bytes: u16,
142 pub transform_kind: u16,
144 pub transform_seed: u64,
146 pub row_count: u32,
148 pub reserved: u32,
150}
151
152impl VectorHeader {
153 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#[derive(Clone, Copy, Debug, PartialEq)]
181#[repr(C)]
182pub struct Int8Factors {
183 pub scale: f32,
185 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}