Skip to main content

mlt_core/decoder/
iterators.rs

1//! Zero-copy per-feature view into a fully-decoded [`Layer01<Parsed>`].
2//!
3//! [`ParsedLayer01::iter_features`] yields one [`FeatureRef`] per feature via
4//! [`LendingIterator`].  [`FeatureRef::iter_properties`] exposes per-feature
5//! property values as flat [`ColumnRef`] items; `SharedDict` columns are
6//! transparently expanded and null values are skipped.
7//!
8//! # Iterator model
9//!
10//! Feature iteration uses [`LendingIterator`] rather than [`std::iter::Iterator`].
11//! This allows the iterator to reuse an internal buffer across steps - the
12//! [`FeatureRef`] borrows its property values from that buffer - eliminating a
13//! per-feature `Vec` allocation.
14//!
15//! The consequence is that each [`FeatureRef`] must be dropped before calling
16//! [`LendingIterator::next`] again, so standard adapters like `.map()` and
17//! `.collect()` are **not** available directly.  Use a `while let` loop instead:
18
19use std::fmt;
20use std::iter::FusedIterator;
21use std::ops::Range;
22
23use geo_types::Geometry;
24use usize_cast::IntoUsize as _;
25
26use crate::decoder::{Layer01, ParsedLayer01, ParsedProperty, ParsedScalar, Property, RawProperty};
27use crate::{Lazy, LazyParsed, MltResult, Parsed};
28
29/// A minimal lending (streaming) iterator trait.
30///
31/// Unlike [`std::iter::Iterator`], the item type may borrow from the iterator
32/// itself, enabling zero-allocation iteration where the inner buffer is reused
33/// across steps.
34///
35/// Use a `while let` loop to drive the iterator:
36/// ```ignore
37/// let mut iter = layer.iter_features();
38/// while let Some(feat) = iter.next() {
39///     let feat = feat?;
40///     /* use feat here - it borrows from iter */
41/// }
42/// ```
43pub trait LendingIterator {
44    /// The type of each element, which may borrow from `self`.
45    type Item<'this>
46    where
47        Self: 'this;
48
49    /// Advance the iterator, returning the next element or `None` when exhausted.
50    fn next(&mut self) -> Option<Self::Item<'_>>;
51}
52
53impl<'a> Layer01<'a, Lazy> {
54    /// Iterate over the property column names of this layer, in order.
55    ///
56    /// Regular columns yield one [`PropName`]; `SharedDict` columns yield one name per
57    /// sub-item.  Names are available even before any column data has been decoded.
58    ///
59    /// Pair with [`FeatureRef::iter_all_properties`] to associate per-feature
60    /// values with their column names.
61    pub fn iterate_prop_names(&self) -> PropNamesIter<'_, Property<'a, Lazy>> {
62        PropNamesIter::new(&self.properties)
63    }
64}
65
66impl<'a> ParsedLayer01<'a> {
67    /// Iterate over all features in this fully-decoded layer via a [`LendingIterator`].
68    ///
69    /// Yields one `MltResult<`[`FeatureRef`]`>` per feature. Geometry decoding can
70    /// fail, hence the `Result` wrapper.
71    ///
72    /// ```text
73    /// let mut iter = parsed.iter_features();
74    /// while let Some(feat) = iter.next() {
75    ///     let feat = feat?;
76    ///     for col in feat.iter_properties() {
77    ///        // or use iter_all_properties() to include Nones
78    ///     }
79    /// }
80    /// ```
81    ///
82    /// All inner iterators - [`FeatureRef::iter_properties`],
83    /// [`FeatureRef::iter_all_properties`], and the name iterators - implement the
84    /// standard [`std::iter::Iterator`] trait and compose normally.
85    #[must_use]
86    pub fn iter_features(&self) -> Layer01FeatureIter<'_, 'a> {
87        Layer01FeatureIter::new(self)
88    }
89
90    /// Iterate over the property column names of this layer, in order.
91    /// See [`Layer01::iterate_prop_names`] for details.
92    pub fn iterate_prop_names(&self) -> PropNamesIter<'_, ParsedProperty<'a>> {
93        PropNamesIter::new(&self.properties)
94    }
95}
96
97/// A zero-allocation two-part property name yielded by [`FeatureRef::iter_properties`].
98///
99/// The two parts concatenate on [`Display`](fmt::Display) as `"{}{}"`:
100/// - For regular columns: `(column_name, "")` - zero allocation, second part always empty.
101/// - For `SharedDict` sub-items: `(prefix, suffix)` - both borrow directly from layer data.
102///
103/// Structural [`PartialEq`] compares both parts independently.  Use [`PartialEq<str>`] or
104/// [`PartialEq<&str>`] (also implemented) to compare against a plain `&str` as if the two
105/// parts were concatenated.
106#[derive(Debug, Clone, Copy)] // WARN: do not auto-derive PartialEq,Eq,Hash as it won't be correct
107pub struct PropName<'a>(&'a str, &'a str);
108
109impl fmt::Display for PropName<'_> {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        f.write_str(self.0)?;
112        f.write_str(self.1)
113    }
114}
115
116impl PartialEq<PropName<'_>> for PropName<'_> {
117    fn eq(&self, other: &PropName<'_>) -> bool {
118        // Compare the concatenated strings byte-by-byte without allocating.
119        let (a0, a1) = (self.0.as_bytes(), self.1.as_bytes());
120        let a = a0.iter().chain(a1);
121        let (b0, b1) = (other.0.as_bytes(), other.1.as_bytes());
122        let b = b0.iter().chain(b1);
123        let combined_len_eq = a0.len() + a1.len() == b0.len() + b1.len();
124        combined_len_eq && a.eq(b)
125    }
126}
127
128impl PartialEq<str> for PropName<'_> {
129    /// Returns `true` if `other == self.0 + self.1`.
130    fn eq(&self, other: &str) -> bool {
131        other.strip_prefix(self.0) == Some(self.1)
132    }
133}
134
135impl PartialEq<PropName<'_>> for str {
136    fn eq(&self, other: &PropName<'_>) -> bool {
137        other == self
138    }
139}
140
141impl PartialEq<&str> for PropName<'_> {
142    fn eq(&self, other: &&str) -> bool {
143        self == *other
144    }
145}
146
147impl PartialEq<PropName<'_>> for &str {
148    fn eq(&self, other: &PropName<'_>) -> bool {
149        other == *self
150    }
151}
152
153/// A borrowed, non-null per-feature property value.
154///
155/// Nullability is lifted to [`ColumnRef`]: only non-null values appear in
156/// [`FeatureRef::iter_properties`].
157#[derive(Debug, Clone, Copy, PartialEq)]
158pub enum PropValueRef<'a> {
159    Bool(bool),
160    I8(i8),
161    U8(u8),
162    I32(i32),
163    U32(u32),
164    I64(i64),
165    U64(u64),
166    F32(f32),
167    F64(f64),
168    Str(&'a str),
169}
170
171macro_rules! impl_from_for_prop_value_ref {
172    ($($ty:ty => $variant:ident),+ $(,)?) => {
173        $(impl From<$ty> for PropValueRef<'_> {
174            fn from(v: $ty) -> Self { Self::$variant(v) }
175        })+
176    };
177}
178impl_from_for_prop_value_ref!(
179    bool => Bool, i8 => I8, u8 => U8,
180    i32 => I32, u32 => U32,
181    i64 => I64, u64 => U64,
182    f32 => F32, f64 => F64,
183);
184
185/// A single non-null property value for one feature, yielded by [`FeatureRef::iter_properties`].
186///
187/// `name` is a [`PropName`] that displays as `"{prefix}{suffix}"`.
188/// All borrows are zero-copy from the layer data.
189#[derive(Debug, Clone, Copy, PartialEq)]
190pub struct ColumnRef<'a> {
191    name: PropName<'a>,
192    value: PropValueRef<'a>,
193}
194
195impl<'a> ColumnRef<'a> {
196    #[must_use]
197    pub fn name(&self) -> PropName<'a> {
198        self.name
199    }
200
201    #[must_use]
202    pub fn value(&self) -> PropValueRef<'a> {
203        self.value
204    }
205}
206
207/// A single map feature returned by [`ParsedLayer01::iter_features`].
208///
209/// Borrows `values` from the outer [`Layer01FeatureIter`] buffer - it must be
210/// dropped before calling [`LendingIterator::next`] again.
211#[derive(Debug)]
212pub struct FeatureRef<'feat, 'layer: 'feat> {
213    /// Optional feature ID.
214    id: Option<u64>,
215    /// Geometry in [`Geometry<i32>`] form (owned, decoded on demand by the iterator).
216    geometry: Geometry<i32>,
217    /// Borrowed slice of column descriptors from the layer; used to yield column names.
218    columns: &'layer [ParsedProperty<'layer>],
219    /// Per-feature values in column order, one per slot (scalar, string, or `SharedDict`
220    /// sub-item).  Borrowed from the iterator's reused buffer - no allocation per feature.
221    values: &'feat [Option<PropValueRef<'layer>>],
222}
223
224impl<'feat, 'layer: 'feat> FeatureRef<'feat, 'layer> {
225    #[must_use]
226    pub fn id(&self) -> Option<u64> {
227        self.id
228    }
229
230    #[must_use]
231    pub fn geometry(&self) -> &Geometry<i32> {
232        &self.geometry
233    }
234
235    /// Iterate over every property slot for this feature, **values only**, in column order.
236    ///
237    /// Yields `Option<PropValueRef>`:
238    /// - `Some(value)` - the slot contains a non-null value.
239    /// - `None` - the slot is null / absent.
240    ///
241    /// Use [`Layer01::iterate_prop_names`] to pair values with their column names.
242    #[must_use]
243    pub fn iter_all_properties(
244        &self,
245    ) -> impl ExactSizeIterator<Item = Option<PropValueRef<'layer>>>
246    + DoubleEndedIterator
247    + FusedIterator
248    + '_ {
249        self.values.iter().copied()
250    }
251
252    /// Iterate over all non-null properties for this feature.
253    ///
254    /// `SharedDict` columns are transparently expanded into one [`ColumnRef`] per sub-item.
255    /// Null / absent values are skipped entirely. The iterator is infallible.
256    #[must_use]
257    pub fn iter_properties(
258        &self,
259    ) -> impl DoubleEndedIterator<Item = ColumnRef<'layer>> + FusedIterator + '_ {
260        PropNamesIter::new(self.columns)
261            .zip(self.values.iter().copied())
262            .filter_map(|(name, opt_val)| opt_val.map(|value| ColumnRef { name, value }))
263    }
264
265    /// Look up a property by name, returning its value if present and non-null.
266    ///
267    /// For `SharedDict` columns the expected name is `"{prefix}{suffix}"`, matching
268    /// the key used by [`iter_properties`](Self::iter_properties).
269    #[must_use]
270    pub fn get_property(&self, name: &str) -> Option<PropValueRef<'layer>> {
271        self.iter_properties()
272            .find(|col| col.name() == name)
273            .map(|col| col.value())
274    }
275}
276
277// ── Column name helpers ───────────────────────────────────────────────────────
278
279/// A property column that contributes one or more [`PropName`]s.
280///
281/// Scalar and string columns contribute exactly one name; `SharedDict` columns
282/// contribute one per sub-item.
283pub trait ColNames {
284    /// Always `PropName<'tile>`.  It cannot be written as `PropName<'_>` directly:
285    /// that would tie names to the `&self` borrow rather than to the tile buffer,
286    /// so they could no longer outlive the layer.
287    type Name;
288
289    /// Number of names this column contributes.
290    fn name_count(&self) -> usize;
291
292    /// The name at sub-index `idx`, which must be less than [`Self::name_count`].
293    fn name_at(&self, idx: usize) -> Self::Name;
294}
295
296impl<'p> ColNames for ParsedProperty<'p> {
297    type Name = PropName<'p>;
298
299    fn name_count(&self) -> usize {
300        match self {
301            Self::SharedDict(sd) => sd.items.len(),
302            Self::Bool(_)
303            | Self::I8(_)
304            | Self::U8(_)
305            | Self::I32(_)
306            | Self::U32(_)
307            | Self::I64(_)
308            | Self::U64(_)
309            | Self::F32(_)
310            | Self::F64(_)
311            | Self::Str(_) => 1,
312        }
313    }
314
315    fn name_at(&self, idx: usize) -> PropName<'p> {
316        use ParsedProperty as P;
317        match self {
318            P::Bool(s) => PropName(s.name, ""),
319            P::I8(s) => PropName(s.name, ""),
320            P::U8(s) => PropName(s.name, ""),
321            P::I32(s) => PropName(s.name, ""),
322            P::U32(s) => PropName(s.name, ""),
323            P::I64(s) => PropName(s.name, ""),
324            P::U64(s) => PropName(s.name, ""),
325            P::F32(s) => PropName(s.name, ""),
326            P::F64(s) => PropName(s.name, ""),
327            P::Str(s) => PropName(s.name, ""),
328            P::SharedDict(sd) => PropName(sd.prefix, sd.items[idx].suffix),
329        }
330    }
331}
332
333impl<'p> ColNames for RawProperty<'p> {
334    type Name = PropName<'p>;
335
336    fn name_count(&self) -> usize {
337        match self {
338            Self::SharedDict(sd) => sd.children.len(),
339            Self::Bool(_)
340            | Self::I8(_)
341            | Self::U8(_)
342            | Self::I32(_)
343            | Self::U32(_)
344            | Self::I64(_)
345            | Self::U64(_)
346            | Self::F32(_)
347            | Self::F64(_)
348            | Self::Str(_) => 1,
349        }
350    }
351
352    fn name_at(&self, idx: usize) -> PropName<'p> {
353        use RawProperty as P;
354        match self {
355            P::Bool(s)
356            | P::I8(s)
357            | P::U8(s)
358            | P::I32(s)
359            | P::U32(s)
360            | P::I64(s)
361            | P::U64(s)
362            | P::F32(s)
363            | P::F64(s) => PropName(s.name, ""),
364            P::Str(s) => PropName(s.name, ""),
365            P::SharedDict(sd) => PropName(sd.name, sd.children[idx].name),
366        }
367    }
368}
369
370/// A column that failed to parse contributes no names at all.
371impl<'p> ColNames for LazyParsed<RawProperty<'p>, ParsedProperty<'p>> {
372    type Name = PropName<'p>;
373
374    fn name_count(&self) -> usize {
375        match self {
376            Self::Raw(r) => r.name_count(),
377            Self::Parsed(p) => p.name_count(),
378            Self::ParsingFailed => 0,
379        }
380    }
381
382    fn name_at(&self, idx: usize) -> PropName<'p> {
383        match self {
384            Self::Raw(r) => r.name_at(idx),
385            Self::Parsed(p) => p.name_at(idx),
386            Self::ParsingFailed => unreachable!("ParsingFailed contributes no names"),
387        }
388    }
389}
390
391/// Iterates the property column names of a layer, in column order.
392///
393/// Regular columns yield one [`PropName`]; `SharedDict` columns yield one name per
394/// sub-item (`(prefix, suffix)`).
395#[must_use]
396pub struct PropNamesIter<'a, C> {
397    props: &'a [C],
398    /// Columns that may still yield a name: `cols.start` is the front column,
399    /// `cols.end - 1` the back one.  The two coincide once they meet.
400    cols: Range<usize>,
401    /// Next sub-index to yield from `props[cols.start]`.
402    front_sub: usize,
403    /// One past the next sub-index to yield from `props[cols.end - 1]`.
404    back_sub: usize,
405    /// Names not yet yielded from either end.  Both ends decrement it, so it is
406    /// what stops them crossing while they share a column.
407    remaining: usize,
408}
409
410impl<'a, C: ColNames> PropNamesIter<'a, C> {
411    pub(crate) fn new(props: &'a [C]) -> Self {
412        Self {
413            props,
414            cols: 0..props.len(),
415            front_sub: 0,
416            back_sub: props.last().map_or(0, ColNames::name_count),
417            remaining: props.iter().map(ColNames::name_count).sum(),
418        }
419    }
420}
421
422impl<C: ColNames> Iterator for PropNamesIter<'_, C> {
423    type Item = C::Name;
424
425    fn next(&mut self) -> Option<C::Name> {
426        if self.remaining == 0 {
427            return None;
428        }
429        self.remaining -= 1;
430        loop {
431            // `remaining` was non-zero, so some column in `cols` still has a name.
432            let col = &self.props[self.cols.start];
433            if self.front_sub < col.name_count() {
434                let name = col.name_at(self.front_sub);
435                self.front_sub += 1;
436                return Some(name);
437            }
438            self.cols.start += 1;
439            self.front_sub = 0;
440        }
441    }
442
443    fn size_hint(&self) -> (usize, Option<usize>) {
444        (self.remaining, Some(self.remaining))
445    }
446}
447
448impl<C: ColNames> DoubleEndedIterator for PropNamesIter<'_, C> {
449    fn next_back(&mut self) -> Option<C::Name> {
450        if self.remaining == 0 {
451            return None;
452        }
453        self.remaining -= 1;
454        loop {
455            if self.back_sub > 0 {
456                self.back_sub -= 1;
457                return Some(self.props[self.cols.end - 1].name_at(self.back_sub));
458            }
459            self.cols.end -= 1;
460            self.back_sub = self.props[self.cols.end - 1].name_count();
461        }
462    }
463}
464
465impl<C: ColNames> ExactSizeIterator for PropNamesIter<'_, C> {
466    fn len(&self) -> usize {
467        self.remaining
468    }
469}
470
471impl<C: ColNames> FusedIterator for PropNamesIter<'_, C> {}
472
473/// A boxed per-column-slot value iterator yielding one `Option<`[`PropValueRef`]`>` per feature.
474type ColValIter<'l> = Box<dyn Iterator<Item = Option<PropValueRef<'l>>> + 'l>;
475
476/// Build one [`ColValIter`] per property column "slot" from a decoded column slice.
477///
478/// - Scalar and string columns contribute one slot each.
479/// - `SharedDict` columns contribute one slot per sub-item.
480fn build_col_iters<'p>(columns: &'p [ParsedProperty<'p>]) -> Vec<ColValIter<'p>> {
481    use ParsedProperty as PP;
482    let mut iters: Vec<ColValIter<'p>> = Vec::new();
483    for col in columns {
484        match col {
485            PP::Bool(s) => iters.push(scalar_col_iter(s)),
486            PP::I8(s) => iters.push(scalar_col_iter(s)),
487            PP::U8(s) => iters.push(scalar_col_iter(s)),
488            PP::I32(s) => iters.push(scalar_col_iter(s)),
489            PP::U32(s) => iters.push(scalar_col_iter(s)),
490            PP::I64(s) => iters.push(scalar_col_iter(s)),
491            PP::U64(s) => iters.push(scalar_col_iter(s)),
492            PP::F32(s) => iters.push(scalar_col_iter(s)),
493            PP::F64(s) => iters.push(scalar_col_iter(s)),
494            PP::Str(strings) => {
495                let data: &'p str = strings.data.as_ref();
496                let lengths: &'p [i32] = &strings.lengths;
497                let mut curr_end: usize = 0;
498                let mut feat_idx = 0usize;
499                iters.push(Box::new(std::iter::from_fn(move || {
500                    let &end_i32 = lengths.get(feat_idx)?;
501                    feat_idx += 1;
502                    if end_i32 >= 0 {
503                        let start = curr_end;
504                        curr_end = end_i32.cast_unsigned().into_usize();
505                        Some(data.get(start..curr_end).map(PropValueRef::Str))
506                    } else {
507                        // Null slot: curr_end unchanged (null encodes the current byte offset).
508                        Some(None)
509                    }
510                })));
511            }
512            PP::SharedDict(dict) => {
513                for item in &dict.items {
514                    let dict_ref: &'p _ = dict;
515                    let item_ref: &'p _ = item;
516                    let mut feat_idx = 0usize;
517                    iters.push(Box::new(std::iter::from_fn(move || {
518                        if feat_idx >= item_ref.ranges.len() {
519                            return None;
520                        }
521                        let idx = feat_idx;
522                        feat_idx += 1;
523                        Some(item_ref.get(dict_ref, idx).map(PropValueRef::Str))
524                    })));
525                }
526            }
527        }
528    }
529    iters
530}
531
532/// Build a boxed value iterator for a single scalar property column.
533fn scalar_col_iter<'p, T>(scalar: &'p ParsedScalar<'p, T>) -> ColValIter<'p>
534where
535    T: Copy + PartialEq,
536    PropValueRef<'p>: From<T>,
537{
538    Box::new(scalar.iter_optional().map(|o| o.map(PropValueRef::from)))
539}
540
541/// Iterator over the features of a fully-decoded [`Layer01<Parsed>`].
542///
543/// Returned by [`ParsedLayer01::iter_features`]. Implements [`LendingIterator`]:
544/// advance with `while let Some(feat) = iter.next()`.
545///
546/// Holds one O(1)-per-step cursor per property column slot. On each step the
547/// per-column cursors are advanced and their results written into a reused
548/// `values_buf` - yielding a [`FeatureRef`] that borrows that buffer with no
549/// per-feature heap allocation.
550pub struct Layer01FeatureIter<'layer, 'data: 'layer> {
551    layer: &'layer Layer01<'data, Parsed>,
552    index: usize,
553    feature_count: usize,
554    /// ID iterator, `None` when the layer has no ID column.
555    id_iter: Option<crate::utils::PresenceOptIter<'layer, u64>>,
556    /// One boxed value iterator per column slot (scalar, string, or `SharedDict` sub-item).
557    col_iters: Vec<ColValIter<'layer>>,
558    /// Reused buffer: filled on each `next()` call, borrowed by the yielded [`FeatureRef`].
559    values_buf: Vec<Option<PropValueRef<'layer>>>,
560}
561
562impl<'layer, 'data: 'layer> Layer01FeatureIter<'layer, 'data> {
563    fn new(layer: &'layer Layer01<'data, Parsed>) -> Self {
564        let col_iters = build_col_iters(&layer.properties);
565        let cap = col_iters.len();
566        Self {
567            layer,
568            index: 0,
569            feature_count: layer.feature_count(),
570            id_iter: layer.id.as_ref().map(|id| id.iter_optional()),
571            col_iters,
572            values_buf: Vec::with_capacity(cap),
573        }
574    }
575
576    /// Number of features not yet yielded.
577    #[must_use]
578    pub fn len(&self) -> usize {
579        self.feature_count - self.index
580    }
581
582    /// Returns `true` if all features have been yielded.
583    #[must_use]
584    pub fn is_empty(&self) -> bool {
585        self.index >= self.feature_count
586    }
587}
588
589impl<'layer> LendingIterator for Layer01FeatureIter<'layer, '_> {
590    type Item<'this>
591        = MltResult<FeatureRef<'this, 'layer>>
592    where
593        Self: 'this;
594
595    fn next(&mut self) -> Option<Self::Item<'_>> {
596        let index = self.index;
597        if index >= self.feature_count {
598            return None;
599        }
600        self.index += 1;
601
602        // Advance all per-feature cursors unconditionally, even if geometry decode fails,
603        // so that IDs and property values remain aligned with geometry indices.
604        let id = self.id_iter.as_mut().and_then(Iterator::next).flatten();
605        self.values_buf.clear();
606        self.values_buf
607            .extend(self.col_iters.iter_mut().map(|it| it.next().flatten()));
608
609        Some(
610            self.layer
611                .geometry
612                .to_geojson(index)
613                .map(|geometry| FeatureRef {
614                    id,
615                    geometry,
616                    columns: &self.layer.properties,
617                    values: &self.values_buf,
618                }),
619        )
620    }
621}
622
623#[cfg(test)]
624mod tests {
625    use geo_types::Point;
626    use serde_json::Value;
627
628    use super::*;
629    use crate::Layer;
630    use crate::decoder::GeometryValues;
631    use crate::encoder::model::StagedLayer;
632    use crate::encoder::{Codecs, Encoder, Presence, StagedId, StagedProperty, StagedSharedDict};
633    use crate::test_helpers::{assert_size_hint_exact, dec, parser};
634
635    fn layer_buf(staged: StagedLayer) -> Vec<u8> {
636        staged
637            .encode_into(Encoder::default(), &mut Codecs::default())
638            .unwrap()
639            .into_layer_bytes()
640            .unwrap()
641    }
642
643    fn three_points() -> GeometryValues {
644        let mut g = GeometryValues::default();
645        g.push_geom(&Geometry::<i32>::Point(Point::new(1, 2)));
646        g.push_geom(&Geometry::<i32>::Point(Point::new(3, 4)));
647        g.push_geom(&Geometry::<i32>::Point(Point::new(5, 6)));
648        g
649    }
650
651    fn empty_layer(name: &str) -> StagedLayer {
652        staged_layer(name, StagedId::None, GeometryValues::default(), vec![])
653    }
654
655    fn staged_layer(
656        name: &str,
657        id: StagedId,
658        geometry: GeometryValues,
659        properties: Vec<StagedProperty>,
660    ) -> StagedLayer {
661        StagedLayer::new(name, 4096, id, geometry, properties).unwrap()
662    }
663
664    #[test]
665    fn prop_name_display_concatenates_parts() {
666        assert_eq!(PropName("addr:", "city").to_string(), "addr:city");
667        assert_eq!(PropName("name", "").to_string(), "name");
668        assert_eq!(PropName("", "").to_string(), "");
669    }
670
671    #[test]
672    fn prop_name_eq_str_matches_concatenation() {
673        assert_eq!(PropName("addr:", "city"), "addr:city");
674        assert_eq!("addr:city", PropName("addr:", "city"));
675        assert_ne!(PropName("addr:", "city"), "addr:");
676        assert_ne!(PropName("addr:", "city"), "city");
677        assert_eq!(PropName("name", ""), "name");
678    }
679
680    #[test]
681    fn prop_name_structural_eq_is_part_wise() {
682        assert_eq!(PropName("a", "b"), PropName("a", "b"));
683        assert_eq!(PropName("ab", ""), PropName("a", "b"));
684    }
685
686    #[test]
687    fn prop_name_eq_prop_name_semantic_equality() {
688        assert_eq!(PropName("ab", ""), PropName("a", "b"));
689        assert_eq!(PropName("", "ab"), PropName("a", "b"));
690        assert_eq!(PropName("abc", "def"), PropName("ab", "cdef"));
691        assert_eq!(PropName("a", "bcdef"), PropName("abcde", "f"));
692
693        assert_ne!(PropName("a", "b"), PropName("a", "c"));
694        assert_ne!(PropName("a", "b"), PropName("ab", "c"));
695        assert_ne!(PropName("abc", ""), PropName("ab", ""));
696    }
697
698    #[test]
699    fn prop_value_ref_scalars_convert_to_json() {
700        assert_eq!(Value::from(PropValueRef::Bool(true)), Value::Bool(true));
701        assert_eq!(Value::from(PropValueRef::Bool(false)), Value::Bool(false));
702        assert_eq!(Value::from(PropValueRef::I8(-1)), Value::from(-1_i8));
703        assert_eq!(Value::from(PropValueRef::U8(255)), Value::from(255_u8));
704        assert_eq!(
705            Value::from(PropValueRef::I32(-1000)),
706            Value::from(-1000_i32)
707        );
708        assert_eq!(Value::from(PropValueRef::U32(1000)), Value::from(1000_u32));
709        assert_eq!(
710            Value::from(PropValueRef::I64(i64::MIN)),
711            Value::from(i64::MIN)
712        );
713        assert_eq!(
714            Value::from(PropValueRef::U64(u64::MAX)),
715            Value::from(u64::MAX)
716        );
717        assert_eq!(
718            Value::from(PropValueRef::Str("hello")),
719            Value::String("hello".into())
720        );
721    }
722
723    #[test]
724    fn prop_value_ref_float_finite_is_number() {
725        assert!(matches!(
726            Value::from(PropValueRef::F32(1.5)),
727            Value::Number(_)
728        ));
729        assert!(matches!(
730            Value::from(PropValueRef::F64(2.5)),
731            Value::Number(_)
732        ));
733    }
734
735    #[test]
736    fn prop_value_ref_float_non_finite_becomes_string_sentinel() {
737        assert_eq!(
738            Value::from(PropValueRef::F32(f32::NAN)),
739            Value::String("f32::NAN".into())
740        );
741        assert_eq!(
742            Value::from(PropValueRef::F32(f32::INFINITY)),
743            Value::String("f32::INFINITY".into())
744        );
745        assert_eq!(
746            Value::from(PropValueRef::F64(f64::NAN)),
747            Value::String("f64::NAN".into())
748        );
749        assert_eq!(
750            Value::from(PropValueRef::F64(f64::NEG_INFINITY)),
751            Value::String("f64::NEG_INFINITY".into())
752        );
753    }
754
755    #[test]
756    fn empty_layer_yields_no_features() {
757        let buf = layer_buf(empty_layer("empty"));
758        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
759        let Layer::Tag01(lazy) = layer else {
760            panic!("expected Tag01")
761        };
762        let parsed = lazy.decode_all(&mut dec()).unwrap();
763
764        let iter = parsed.iter_features();
765        assert_eq!(iter.len(), 0);
766        assert!(iter.is_empty());
767        assert_eq!(parsed.iter_features().len(), 0);
768    }
769
770    #[test]
771    fn len_decreases_with_each_next() {
772        let buf = layer_buf(staged_layer("test", StagedId::None, three_points(), vec![]));
773        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
774        let Layer::Tag01(lazy) = layer else { panic!() };
775        let parsed = lazy.decode_all(&mut dec()).unwrap();
776
777        let mut iter = parsed.iter_features();
778        assert_eq!(iter.len(), 3);
779        iter.next().unwrap().unwrap();
780        assert_eq!(iter.len(), 2);
781        iter.next().unwrap().unwrap();
782        assert_eq!(iter.len(), 1);
783        iter.next().unwrap().unwrap();
784        assert_eq!(iter.len(), 0);
785        assert!(iter.is_empty());
786        assert!(iter.next().is_none());
787    }
788
789    #[test]
790    fn feature_ids_are_preserved() {
791        let buf = layer_buf(staged_layer(
792            "test",
793            StagedId::from_optional(vec![Some(100), None, Some(200)]),
794            three_points(),
795            vec![],
796        ));
797        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
798        let Layer::Tag01(lazy) = layer else { panic!() };
799        let parsed = lazy.decode_all(&mut dec()).unwrap();
800
801        let mut ids = Vec::new();
802        let mut iter = parsed.iter_features();
803        while let Some(r) = iter.next() {
804            ids.push(r.unwrap().id);
805        }
806        assert_eq!(ids, [Some(100), None, Some(200)]);
807    }
808
809    #[test]
810    fn geometry_values_match_input() {
811        let buf = layer_buf(staged_layer("test", StagedId::None, three_points(), vec![]));
812        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
813        let Layer::Tag01(lazy) = layer else { panic!() };
814        let parsed = lazy.decode_all(&mut dec()).unwrap();
815
816        let mut geoms = Vec::new();
817        let mut iter = parsed.iter_features();
818        while let Some(r) = iter.next() {
819            geoms.push(r.unwrap().geometry);
820        }
821        assert_eq!(geoms[0], Geometry::<i32>::Point(Point::new(1, 2)));
822        assert_eq!(geoms[1], Geometry::<i32>::Point(Point::new(3, 4)));
823        assert_eq!(geoms[2], Geometry::<i32>::Point(Point::new(5, 6)));
824    }
825
826    #[test]
827    fn null_scalar_values_are_skipped() {
828        let buf = layer_buf(staged_layer(
829            "test",
830            StagedId::None,
831            three_points(),
832            vec![StagedProperty::opt_u32("n", vec![Some(1), None, Some(3)])],
833        ));
834        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
835        let Layer::Tag01(lazy) = layer else { panic!() };
836        let parsed = lazy.decode_all(&mut dec()).unwrap();
837
838        let mut iter = parsed.iter_features();
839
840        {
841            let feat = iter.next().unwrap().unwrap();
842            let cols: Vec<_> = feat.iter_properties().collect();
843            assert_eq!(cols.len(), 1);
844            assert_eq!(cols[0].name, PropName("n", ""));
845            assert_eq!(cols[0].name, "n");
846            assert_eq!(cols[0].value, PropValueRef::U32(1));
847            let all: Vec<_> = feat.iter_all_properties().collect();
848            assert_eq!(all, [Some(PropValueRef::U32(1))]);
849        }
850        {
851            let feat = iter.next().unwrap().unwrap();
852            assert!(feat.iter_properties().next().is_none());
853            let all: Vec<_> = feat.iter_all_properties().collect();
854            assert_eq!(all, [None]);
855        }
856        {
857            let feat = iter.next().unwrap().unwrap();
858            assert_eq!(feat.get_property("n"), Some(PropValueRef::U32(3)));
859            let all: Vec<_> = feat.iter_all_properties().collect();
860            assert_eq!(all, [Some(PropValueRef::U32(3))]);
861        }
862
863        let names: Vec<_> = parsed.iterate_prop_names().map(|n| n.to_string()).collect();
864        assert_eq!(names, ["n"]);
865    }
866
867    #[test]
868    fn null_string_values_are_skipped() {
869        let buf = layer_buf(staged_layer(
870            "test",
871            StagedId::None,
872            three_points(),
873            vec![StagedProperty::opt_str(
874                "label",
875                vec![Some("foo"), None, Some("bar")],
876            )],
877        ));
878        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
879        let Layer::Tag01(lazy) = layer else { panic!() };
880        let parsed = lazy.decode_all(&mut dec()).unwrap();
881
882        let mut iter = parsed.iter_features();
883        {
884            let feat = iter.next().unwrap().unwrap();
885            assert_eq!(feat.get_property("label"), Some(PropValueRef::Str("foo")));
886        }
887        {
888            let feat = iter.next().unwrap().unwrap();
889            assert_eq!(feat.get_property("label"), None);
890        }
891        {
892            let feat = iter.next().unwrap().unwrap();
893            assert_eq!(feat.get_property("label"), Some(PropValueRef::Str("bar")));
894        }
895    }
896
897    #[test]
898    fn multiple_columns_independently_nullable() {
899        let buf = layer_buf(staged_layer(
900            "test",
901            StagedId::None,
902            three_points(),
903            vec![
904                StagedProperty::opt_bool("flag", vec![Some(true), Some(false), None]),
905                StagedProperty::opt_i32("score", vec![None, Some(-5), Some(7)]),
906            ],
907        ));
908        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
909        let Layer::Tag01(lazy) = layer else { panic!() };
910        let parsed = lazy.decode_all(&mut dec()).unwrap();
911
912        let mut iter = parsed.iter_features();
913
914        // feat 0: flag=true, score=null -> 1 property
915        {
916            let feat = iter.next().unwrap().unwrap();
917            assert_eq!(feat.iter_properties().count(), 1);
918            assert_eq!(feat.get_property("flag"), Some(PropValueRef::Bool(true)));
919            assert_eq!(feat.get_property("score"), None);
920        }
921        // feat 1: flag=false, score=-5 -> 2 properties
922        {
923            let feat = iter.next().unwrap().unwrap();
924            assert_eq!(feat.iter_properties().count(), 2);
925            assert_eq!(feat.get_property("flag"), Some(PropValueRef::Bool(false)));
926            assert_eq!(feat.get_property("score"), Some(PropValueRef::I32(-5)));
927        }
928        // feat 2: flag=null, score=7 -> 1 property
929        {
930            let feat = iter.next().unwrap().unwrap();
931            assert_eq!(feat.iter_properties().count(), 1);
932            assert_eq!(feat.get_property("flag"), None);
933            assert_eq!(feat.get_property("score"), Some(PropValueRef::I32(7)));
934        }
935    }
936
937    #[test]
938    fn geometry_error_does_not_misalign_ids() {
939        use crate::decoder::GeometryType;
940
941        let buf = layer_buf(staged_layer(
942            "test",
943            StagedId::from_optional(vec![Some(10), Some(20), Some(30)]),
944            three_points(),
945            vec![],
946        ));
947        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
948        let Layer::Tag01(lazy) = layer else { panic!() };
949        let mut parsed = lazy.decode_all(&mut dec()).unwrap();
950
951        // Corrupt feature 1's geometry type: Point -> LineString.
952        // A LineString requires part_offsets, which are absent here, so
953        // to_geojson(1) will return Err(NoPartOffsets).
954        parsed.geometry.vector_types[1] = GeometryType::LineString;
955
956        let mut iter = parsed.iter_features();
957
958        // Feature 0: valid Point, id = Some(10)
959        let feat0 = iter.next().unwrap().unwrap();
960        assert_eq!(feat0.id, Some(10));
961
962        // Feature 1: geometry error - iterator still advances ID cursor
963        assert!(iter.next().unwrap().is_err());
964
965        // Feature 2: valid Point, id must be Some(30), not Some(20)
966        let feat2 = iter.next().unwrap().unwrap();
967        assert_eq!(
968            feat2.id,
969            Some(30),
970            "id cursor was not advanced on geometry error"
971        );
972
973        assert!(iter.next().is_none());
974    }
975
976    #[test]
977    fn get_property_absent_column_returns_none() {
978        let buf = layer_buf(staged_layer(
979            "test",
980            StagedId::None,
981            three_points(),
982            vec![StagedProperty::u32("x", vec![1, 2, 3])],
983        ));
984        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
985        let Layer::Tag01(lazy) = layer else { panic!() };
986        let parsed = lazy.decode_all(&mut dec()).unwrap();
987
988        let mut iter = parsed.iter_features();
989        let feat = iter.next().unwrap().unwrap();
990        assert_eq!(feat.get_property("no_such_column"), None);
991    }
992
993    #[test]
994    fn shared_dict_columns_are_expanded() {
995        let shared_dict = StagedSharedDict::new(
996            "addr:",
997            [
998                (
999                    "city",
1000                    vec![Some("Paris"), Some("Rome"), None],
1001                    Presence::Mixed,
1002                ),
1003                (
1004                    "zip",
1005                    vec![Some("75001"), None, Some("00100")],
1006                    Presence::Mixed,
1007                ),
1008            ],
1009        )
1010        .unwrap();
1011
1012        let buf = layer_buf(staged_layer(
1013            "test",
1014            StagedId::None,
1015            three_points(),
1016            vec![StagedProperty::SharedDict(shared_dict)],
1017        ));
1018        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
1019        let Layer::Tag01(lazy) = layer else { panic!() };
1020        let parsed = lazy.decode_all(&mut dec()).unwrap();
1021
1022        let mut iter = parsed.iter_features();
1023
1024        // feat 0: city=Paris, zip=75001
1025        {
1026            let feat = iter.next().unwrap().unwrap();
1027            assert_eq!(
1028                feat.get_property("addr:city"),
1029                Some(PropValueRef::Str("Paris"))
1030            );
1031            assert_eq!(
1032                feat.get_property("addr:zip"),
1033                Some(PropValueRef::Str("75001"))
1034            );
1035            assert_eq!(feat.iter_properties().count(), 2);
1036        }
1037        // feat 1: city=Rome, zip=null
1038        {
1039            let feat = iter.next().unwrap().unwrap();
1040            assert_eq!(
1041                feat.get_property("addr:city"),
1042                Some(PropValueRef::Str("Rome"))
1043            );
1044            assert_eq!(feat.get_property("addr:zip"), None);
1045            assert_eq!(feat.iter_properties().count(), 1);
1046            // iter_all_properties: values only (no names); SharedDict expands to two slots
1047            let all: Vec<_> = feat.iter_all_properties().collect();
1048            assert_eq!(all, [Some(PropValueRef::Str("Rome")), None]);
1049        }
1050        // feat 2: city=null, zip=00100
1051        {
1052            let feat = iter.next().unwrap().unwrap();
1053            assert_eq!(feat.get_property("addr:city"), None);
1054            assert_eq!(
1055                feat.get_property("addr:zip"),
1056                Some(PropValueRef::Str("00100"))
1057            );
1058        }
1059
1060        let names: Vec<_> = parsed.iterate_prop_names().map(|n| n.to_string()).collect();
1061        assert_eq!(names, ["addr:city", "addr:zip"]);
1062    }
1063
1064    fn strs<'p>(iter: impl Iterator<Item = PropName<'p>>) -> Vec<String> {
1065        iter.map(|n| n.to_string()).collect()
1066    }
1067
1068    fn reversed<'p>(iter: impl DoubleEndedIterator<Item = PropName<'p>>) -> Vec<String> {
1069        let mut names = strs(iter.rev());
1070        names.reverse();
1071        names
1072    }
1073
1074    fn both_ends_layer() -> Vec<u8> {
1075        let shared_dict = StagedSharedDict::new(
1076            "addr:",
1077            [
1078                ("city", vec![Some("Paris"); 3], Presence::AllPresent),
1079                ("zip", vec![Some("75001"); 3], Presence::AllPresent),
1080                ("street", vec![Some("Rue"); 3], Presence::AllPresent),
1081            ],
1082        )
1083        .unwrap();
1084
1085        layer_buf(staged_layer(
1086            "test",
1087            StagedId::None,
1088            three_points(),
1089            vec![
1090                StagedProperty::str("before", ["a", "a", "a"]),
1091                StagedProperty::u32("count", vec![1, 2, 3]),
1092                StagedProperty::SharedDict(shared_dict),
1093                StagedProperty::str("after", ["b", "b", "b"]),
1094            ],
1095        ))
1096    }
1097
1098    const BOTH_ENDS_NAMES: [&str; 6] = [
1099        "before",
1100        "count",
1101        "addr:city",
1102        "addr:zip",
1103        "addr:street",
1104        "after",
1105    ];
1106
1107    fn prop_names() -> Vec<PropName<'static>> {
1108        BOTH_ENDS_NAMES.iter().map(|n| PropName(n, "")).collect()
1109    }
1110
1111    #[test]
1112    fn prop_names_iterate_from_both_ends_lazy() {
1113        let buf = both_ends_layer();
1114        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
1115        let Layer::Tag01(lazy) = layer else { panic!() };
1116
1117        assert_eq!(strs(lazy.iterate_prop_names()), BOTH_ENDS_NAMES);
1118        assert_eq!(reversed(lazy.iterate_prop_names()), BOTH_ENDS_NAMES);
1119        assert_size_hint_exact(|| lazy.iterate_prop_names(), &prop_names());
1120    }
1121
1122    #[test]
1123    fn prop_names_iterate_from_both_ends_parsed() {
1124        let buf = both_ends_layer();
1125        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
1126        let Layer::Tag01(lazy) = layer else { panic!() };
1127        let parsed = lazy.decode_all(&mut dec()).unwrap();
1128
1129        assert_eq!(strs(parsed.iterate_prop_names()), BOTH_ENDS_NAMES);
1130        assert_eq!(reversed(parsed.iterate_prop_names()), BOTH_ENDS_NAMES);
1131
1132        assert_size_hint_exact(|| parsed.iterate_prop_names(), &prop_names());
1133    }
1134
1135    #[test]
1136    fn prop_names_of_layer_without_properties() {
1137        let buf = layer_buf(empty_layer("test"));
1138        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
1139        let Layer::Tag01(lazy) = layer else { panic!() };
1140
1141        assert_eq!(lazy.iterate_prop_names().size_hint(), (0, Some(0)));
1142        assert_eq!(lazy.iterate_prop_names().next(), None);
1143        assert_eq!(lazy.iterate_prop_names().next_back(), None);
1144
1145        let parsed = lazy.decode_all(&mut dec()).unwrap();
1146        assert_eq!(parsed.iterate_prop_names().size_hint(), (0, Some(0)));
1147        assert_eq!(parsed.iterate_prop_names().next(), None);
1148        assert_eq!(parsed.iterate_prop_names().next_back(), None);
1149    }
1150
1151    /// `Layer01<Lazy>` only ever holds `Raw` columns today, so the `Parsed` and
1152    /// `ParsingFailed` arms are exercised against a hand-built column slice.
1153    #[test]
1154    fn prop_names_skip_columns_that_failed_to_parse() {
1155        let buf = both_ends_layer();
1156        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
1157        let Layer::Tag01(lazy) = layer else { panic!() };
1158        let raw = lazy.properties.clone();
1159
1160        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
1161        let Layer::Tag01(lazy) = layer else { panic!() };
1162        let parsed = lazy.decode_all(&mut dec()).unwrap().properties;
1163
1164        let mixed = vec![
1165            raw[0].clone(),
1166            LazyParsed::ParsingFailed,
1167            LazyParsed::Parsed(parsed[2].clone()),
1168            LazyParsed::ParsingFailed,
1169            raw[3].clone(),
1170        ];
1171        let expected = ["before", "addr:city", "addr:zip", "addr:street", "after"];
1172
1173        assert_eq!(strs(PropNamesIter::new(&mixed)), expected);
1174        assert_eq!(reversed(PropNamesIter::new(&mixed)), expected);
1175        assert_size_hint_exact(
1176            || PropNamesIter::new(&mixed),
1177            &expected.map(|n| PropName(n, "")),
1178        );
1179
1180        let all_failed = vec![LazyParsed::ParsingFailed; 3];
1181        assert_eq!(PropNamesIter::new(&all_failed).size_hint(), (0, Some(0)));
1182        assert_eq!(PropNamesIter::new(&all_failed).next(), None);
1183        assert_eq!(PropNamesIter::new(&all_failed).next_back(), None);
1184    }
1185
1186    #[test]
1187    fn feature_property_iterators_run_backwards() {
1188        let buf = both_ends_layer();
1189        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
1190        let Layer::Tag01(lazy) = layer else { panic!() };
1191        let parsed = lazy.decode_all(&mut dec()).unwrap();
1192
1193        let mut iter = parsed.iter_features();
1194        let feat = iter.next().unwrap().unwrap();
1195
1196        let all: Vec<_> = feat.iter_all_properties().collect();
1197        assert_size_hint_exact(|| feat.iter_all_properties(), &all);
1198        let mut all_back: Vec<_> = feat.iter_all_properties().rev().collect();
1199        all_back.reverse();
1200        assert_eq!(all_back, all);
1201
1202        let mut props_back: Vec<_> = feat
1203            .iter_properties()
1204            .rev()
1205            .map(|c| c.name().to_string())
1206            .collect();
1207        props_back.reverse();
1208        assert_eq!(props_back, BOTH_ENDS_NAMES);
1209    }
1210}