Skip to content

Commit 257aa5e

Browse files
nyurikclaude
andauthored
refactor(rust): carve v1 wire-format seams to prep for v2 (#1513)
Prepare for v2 with no wire-format or behavior change - Convert RawPresence from a tuple struct over Option<RawStream> into an enum (AllPresent / Stream) and centralize all presence decoding behind RawPresence::decode_bits / decode_bools. v2 can then add raw-bitfield and shared-presence-group variants without touching downstream code. - Move the v1 stream-header wire codec (stream_type byte, encoding byte, counts, RLE/Morton extras) into decoder/stream/header01.rs, absorbing the stream_type byte codec from physical.rs. StreamMeta / RawStream / IntEncoding constructors move to model.rs as format-independent in-memory types, so a future header02 codec can sit alongside. - Rename parse_struct_children to parse_shared_dict_children to match shared-dictionary terminology used elsewhere (per #1201). --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent bbc05ed commit 257aa5e

11 files changed

Lines changed: 219 additions & 148 deletions

File tree

rust/mlt-core/src/decoder/analyze.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ impl Analyze for RawIdValue<'_> {
7878

7979
impl Analyze for RawPresence<'_> {
8080
fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) {
81-
self.0.for_each_stream(cb);
81+
match self {
82+
Self::AllPresent => {}
83+
Self::Stream(s) => s.for_each_stream(cb),
84+
}
8285
}
8386
}
8487

rust/mlt-core/src/decoder/property/decode.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,36 @@
1+
use std::borrow::Cow;
2+
3+
use bitvec::order::Lsb0;
4+
use bitvec::slice::BitSlice;
5+
16
use crate::decoder::{ParsedProperty, ParsedScalar, RawPresence, RawProperty};
27
use crate::utils::decode_presence;
38
use crate::{Decode, Decoder, MltResult};
49

10+
impl<'a> RawPresence<'a> {
11+
/// Decode into a packed bitvector, or `None` for a non-optional column.
12+
///
13+
/// This is the only place aware of every wire representation of presence;
14+
/// all downstream presence handling goes through the returned bits.
15+
pub(crate) fn decode_bits(
16+
self,
17+
dec: &mut Decoder,
18+
) -> MltResult<Option<Cow<'a, BitSlice<u8, Lsb0>>>> {
19+
match self {
20+
Self::AllPresent => Ok(None),
21+
Self::Stream(s) => Ok(Some(s.decode_bitvec(dec)?)),
22+
}
23+
}
24+
25+
/// Decode into one bool per feature, or `None` for a non-optional column.
26+
pub(crate) fn decode_bools(self, dec: &mut Decoder) -> MltResult<Option<Vec<bool>>> {
27+
match self {
28+
Self::AllPresent => Ok(None),
29+
Self::Stream(s) => Ok(Some(s.decode_bools(dec)?)),
30+
}
31+
}
32+
}
33+
534
impl<'a, T: Copy + PartialEq> ParsedScalar<'a, T> {
635
pub fn from_parts(
736
name: &'a str,

rust/mlt-core/src/decoder/property/model.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,16 @@ pub struct RawFsstData<'a> {
209209
pub corpus: RawStream<'a>,
210210
}
211211

212-
/// Raw presence/nullability stream borrowed from input bytes.
212+
/// Raw presence/nullability data for a column.
213+
///
214+
/// `AllPresent` represents a non-optional column; other variants encode presence in a
215+
/// layer-format-specific way. Decode through [`RawPresence::decode_bits`] or
216+
/// [`RawPresence::decode_bools`] instead of matching on it.
213217
#[derive(Debug, Clone, PartialEq, Default)]
214-
pub struct RawPresence<'a>(pub Option<RawStream<'a>>);
218+
pub enum RawPresence<'a> {
219+
/// Non-optional column — every feature has a value; nothing is stored.
220+
#[default]
221+
AllPresent,
222+
/// Tag `0x01`: bool-RLE presence stream with a full stream header.
223+
Stream(RawStream<'a>),
224+
}

rust/mlt-core/src/decoder/property/strings.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,7 @@ impl<'a> RawStrings<'a> {
280280
/// Decode string property from its encoded column.
281281
pub fn decode(self, dec: &mut Decoder) -> MltResult<ParsedStrings<'a>> {
282282
let name = self.name;
283-
let presence = match self.presence.0 {
284-
Some(s) => Some(s.decode_bools(dec)?),
285-
None => None,
286-
};
283+
let presence = self.presence.decode_bools(dec)?;
287284

288285
let parsed = match self.encoding {
289286
RawStringsEncoding::Plain(plain_data) => {
@@ -448,10 +445,7 @@ impl<'a> RawSharedDict<'a> {
448445
let mut items = Vec::with_capacity(self.children.len());
449446
for child in self.children {
450447
let offsets: Vec<u32> = child.data.decode_u32s(dec)?;
451-
let presence = match child.presence.0 {
452-
Some(s) => Some(s.decode_bools(dec)?),
453-
None => None,
454-
};
448+
let presence = child.presence.decode_bools(dec)?;
455449
let ranges = resolve_dict_spans(&offsets, presence.as_deref(), &dict_spans, dec)?
456450
.into_iter()
457451
.map(|span| match span {

rust/mlt-core/src/decoder/root.rs

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -291,74 +291,74 @@ impl<'a> Layer01<'a, Lazy> {
291291
for column in col_info {
292292
use crate::decoder::RawProperty as RP;
293293

294-
let opt;
294+
let presence;
295295
let value;
296296
let name = column.name.unwrap_or("");
297297

298298
match column.typ {
299299
ColumnType::Id | ColumnType::OptId => {
300-
(input, opt) = parse_optional(column.typ, input, parser)?;
300+
(input, presence) = parse_optional(column.typ, input, parser)?;
301301
(input, value) = RawStream::from_bytes(input, parser)?;
302302
id_column.set_once(Raw(RawId {
303-
presence: RawPresence(opt),
303+
presence,
304304
value: RawIdValue::Id32(value),
305305
}))?;
306306
}
307307
ColumnType::LongId | ColumnType::OptLongId => {
308-
(input, opt) = parse_optional(column.typ, input, parser)?;
308+
(input, presence) = parse_optional(column.typ, input, parser)?;
309309
(input, value) = RawStream::from_bytes(input, parser)?;
310310
id_column.set_once(Raw(RawId {
311-
presence: RawPresence(opt),
311+
presence,
312312
value: RawIdValue::Id64(value),
313313
}))?;
314314
}
315315
ColumnType::Geometry => {
316316
input = parse_geometry_column(input, &mut geometry, parser)?;
317317
}
318318
ColumnType::Bool | ColumnType::OptBool => {
319-
(input, opt) = parse_optional(column.typ, input, parser)?;
319+
(input, presence) = parse_optional(column.typ, input, parser)?;
320320
(input, value) = RawStream::parse_bool(input, parser)?;
321-
properties.push(Raw(RP::Bool(scalar(name, opt, value))));
321+
properties.push(Raw(RP::Bool(RawScalar::new(name, presence, value))));
322322
}
323323
ColumnType::I8 | ColumnType::OptI8 => {
324-
(input, opt) = parse_optional(column.typ, input, parser)?;
324+
(input, presence) = parse_optional(column.typ, input, parser)?;
325325
(input, value) = RawStream::from_bytes(input, parser)?;
326-
properties.push(Raw(RP::I8(scalar(name, opt, value))));
326+
properties.push(Raw(RP::I8(RawScalar::new(name, presence, value))));
327327
}
328328
ColumnType::U8 | ColumnType::OptU8 => {
329-
(input, opt) = parse_optional(column.typ, input, parser)?;
329+
(input, presence) = parse_optional(column.typ, input, parser)?;
330330
(input, value) = RawStream::from_bytes(input, parser)?;
331-
properties.push(Raw(RP::U8(scalar(name, opt, value))));
331+
properties.push(Raw(RP::U8(RawScalar::new(name, presence, value))));
332332
}
333333
ColumnType::I32 | ColumnType::OptI32 => {
334-
(input, opt) = parse_optional(column.typ, input, parser)?;
334+
(input, presence) = parse_optional(column.typ, input, parser)?;
335335
(input, value) = RawStream::from_bytes(input, parser)?;
336-
properties.push(Raw(RP::I32(scalar(name, opt, value))));
336+
properties.push(Raw(RP::I32(RawScalar::new(name, presence, value))));
337337
}
338338
ColumnType::U32 | ColumnType::OptU32 => {
339-
(input, opt) = parse_optional(column.typ, input, parser)?;
339+
(input, presence) = parse_optional(column.typ, input, parser)?;
340340
(input, value) = RawStream::from_bytes(input, parser)?;
341-
properties.push(Raw(RP::U32(scalar(name, opt, value))));
341+
properties.push(Raw(RP::U32(RawScalar::new(name, presence, value))));
342342
}
343343
ColumnType::I64 | ColumnType::OptI64 => {
344-
(input, opt) = parse_optional(column.typ, input, parser)?;
344+
(input, presence) = parse_optional(column.typ, input, parser)?;
345345
(input, value) = RawStream::from_bytes(input, parser)?;
346-
properties.push(Raw(RP::I64(scalar(name, opt, value))));
346+
properties.push(Raw(RP::I64(RawScalar::new(name, presence, value))));
347347
}
348348
ColumnType::U64 | ColumnType::OptU64 => {
349-
(input, opt) = parse_optional(column.typ, input, parser)?;
349+
(input, presence) = parse_optional(column.typ, input, parser)?;
350350
(input, value) = RawStream::from_bytes(input, parser)?;
351-
properties.push(Raw(RP::U64(scalar(name, opt, value))));
351+
properties.push(Raw(RP::U64(RawScalar::new(name, presence, value))));
352352
}
353353
ColumnType::F32 | ColumnType::OptF32 => {
354-
(input, opt) = parse_optional(column.typ, input, parser)?;
354+
(input, presence) = parse_optional(column.typ, input, parser)?;
355355
(input, value) = RawStream::from_bytes(input, parser)?;
356-
properties.push(Raw(RP::F32(scalar(name, opt, value))));
356+
properties.push(Raw(RP::F32(RawScalar::new(name, presence, value))));
357357
}
358358
ColumnType::F64 | ColumnType::OptF64 => {
359-
(input, opt) = parse_optional(column.typ, input, parser)?;
359+
(input, presence) = parse_optional(column.typ, input, parser)?;
360360
(input, value) = RawStream::from_bytes(input, parser)?;
361-
properties.push(Raw(RP::F64(scalar(name, opt, value))));
361+
properties.push(Raw(RP::F64(RawScalar::new(name, presence, value))));
362362
}
363363
ColumnType::Str | ColumnType::OptStr => {
364364
let prop;
@@ -408,26 +408,26 @@ impl<'a> Layer01<'a, Lazy> {
408408
}
409409
}
410410

411-
fn parse_struct_children<'a>(
411+
fn parse_shared_dict_children<'a>(
412412
mut input: &'a [u8],
413413
column: &Column<'a>,
414414
parser: &mut Parser,
415415
) -> MltRefResult<'a, Vec<RawSharedDictItem<'a>>> {
416416
let mut children = Vec::with_capacity(column.children.len());
417417
for child in &column.children {
418418
let (inp, sc) = parse_varint::<u32>(input)?;
419-
let (inp, child_optional) = parse_optional(child.typ, inp, parser)?;
420-
let optional_stream_count = u32::from(child_optional.is_some());
419+
let (inp, presence) = parse_optional(child.typ, inp, parser)?;
420+
let optional_stream_count = u32::from(presence.is_optional());
421421
if let Some(data_count) = sc.checked_sub(optional_stream_count)
422422
&& data_count != 1
423423
{
424424
return Err(UnexpectedStructChildCount(data_count));
425425
}
426-
let (inp, child_data) = RawStream::from_bytes(inp, parser)?;
426+
let (inp, data) = RawStream::from_bytes(inp, parser)?;
427427
children.push(RawSharedDictItem {
428428
name: child.name.unwrap_or(""),
429-
presence: RawPresence(child_optional),
430-
data: child_data,
429+
presence,
430+
data,
431431
});
432432
input = inp;
433433
}
@@ -438,12 +438,12 @@ fn parse_optional<'a>(
438438
typ: ColumnType,
439439
input: &'a [u8],
440440
parser: &mut Parser,
441-
) -> MltRefResult<'a, Option<RawStream<'a>>> {
441+
) -> MltRefResult<'a, RawPresence<'a>> {
442442
if typ.is_optional() {
443443
let (input, optional) = RawStream::parse_bool(input, parser)?;
444-
Ok((input, Some(optional)))
444+
Ok((input, RawPresence::Stream(optional)))
445445
} else {
446-
Ok((input, None))
446+
Ok((input, RawPresence::AllPresent))
447447
}
448448
}
449449

@@ -482,7 +482,7 @@ fn parse_str_column<'a>(
482482
};
483483
let presence;
484484
(input, presence) = parse_optional(typ, input, parser)?;
485-
if presence.is_some() {
485+
if presence.is_optional() {
486486
if stream_count == 0 {
487487
return Err(UnsupportedStringStreamCount(stream_count));
488488
}
@@ -516,7 +516,7 @@ fn parse_str_column<'a>(
516516
input,
517517
RawProperty::Str(RawStrings {
518518
name,
519-
presence: RawPresence(presence),
519+
presence,
520520
encoding,
521521
}),
522522
))
@@ -548,13 +548,13 @@ fn parse_shared_dict_column<'a>(
548548
}
549549
}
550550
let children;
551-
(input, children) = parse_struct_children(input, column, parser)?;
551+
(input, children) = parse_shared_dict_children(input, column, parser)?;
552552

553553
// Validate stream_count: must equal dict_streams + children + optional_children.
554554
let children_n = u32::try_from(children.len()).or_overflow()?;
555555
let optional_n = children
556556
.iter()
557-
.filter(|c| c.presence.0.is_some())
557+
.filter(|c| c.presence.is_optional())
558558
.count()
559559
.try_into()
560560
.or_overflow()?;
@@ -638,11 +638,21 @@ fn parse_columns_meta<'a>(
638638
Ok((input, (col_info, column_count - geometries - ids)))
639639
}
640640

641-
fn scalar<'a>(name: &'a str, opt: Option<RawStream<'a>>, value: RawStream<'a>) -> RawScalar<'a> {
642-
RawScalar {
643-
name,
644-
presence: RawPresence(opt),
645-
data: value,
641+
impl<'a> RawScalar<'a> {
642+
fn new(name: &'a str, presence: RawPresence<'a>, data: RawStream<'a>) -> Self {
643+
Self {
644+
name,
645+
presence,
646+
data,
647+
}
648+
}
649+
}
650+
651+
impl RawPresence<'_> {
652+
/// Whether this column carries presence data (some features may be null).
653+
#[must_use]
654+
pub(crate) fn is_optional(&self) -> bool {
655+
!matches!(self, Self::AllPresent)
646656
}
647657
}
648658

0 commit comments

Comments
 (0)