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            _ => 1,
303        }
304    }
305
306    fn name_at(&self, idx: usize) -> PropName<'p> {
307        use ParsedProperty as P;
308        match self {
309            P::Bool(s) => PropName(s.name, ""),
310            P::I8(s) => PropName(s.name, ""),
311            P::U8(s) => PropName(s.name, ""),
312            P::I32(s) => PropName(s.name, ""),
313            P::U32(s) => PropName(s.name, ""),
314            P::I64(s) => PropName(s.name, ""),
315            P::U64(s) => PropName(s.name, ""),
316            P::F32(s) => PropName(s.name, ""),
317            P::F64(s) => PropName(s.name, ""),
318            P::Str(s) => PropName(s.name, ""),
319            P::SharedDict(sd) => PropName(sd.prefix, sd.items[idx].suffix),
320        }
321    }
322}
323
324impl<'p> ColNames for RawProperty<'p> {
325    type Name = PropName<'p>;
326
327    fn name_count(&self) -> usize {
328        match self {
329            Self::SharedDict(sd) => sd.children.len(),
330            _ => 1,
331        }
332    }
333
334    fn name_at(&self, idx: usize) -> PropName<'p> {
335        use RawProperty as P;
336        match self {
337            P::Bool(s)
338            | P::I8(s)
339            | P::U8(s)
340            | P::I32(s)
341            | P::U32(s)
342            | P::I64(s)
343            | P::U64(s)
344            | P::F32(s)
345            | P::F64(s) => PropName(s.name, ""),
346            P::Str(s) => PropName(s.name, ""),
347            P::SharedDict(sd) => PropName(sd.name, sd.children[idx].name),
348        }
349    }
350}
351
352/// A column that failed to parse contributes no names at all.
353impl<'p> ColNames for LazyParsed<RawProperty<'p>, ParsedProperty<'p>> {
354    type Name = PropName<'p>;
355
356    fn name_count(&self) -> usize {
357        match self {
358            Self::Raw(r) => r.name_count(),
359            Self::Parsed(p) => p.name_count(),
360            Self::ParsingFailed => 0,
361        }
362    }
363
364    fn name_at(&self, idx: usize) -> PropName<'p> {
365        match self {
366            Self::Raw(r) => r.name_at(idx),
367            Self::Parsed(p) => p.name_at(idx),
368            Self::ParsingFailed => unreachable!("ParsingFailed contributes no names"),
369        }
370    }
371}
372
373/// Iterates the property column names of a layer, in column order.
374///
375/// Regular columns yield one [`PropName`]; `SharedDict` columns yield one name per
376/// sub-item (`(prefix, suffix)`).
377#[must_use]
378pub struct PropNamesIter<'a, C> {
379    props: &'a [C],
380    /// Columns that may still yield a name: `cols.start` is the front column,
381    /// `cols.end - 1` the back one.  The two coincide once they meet.
382    cols: Range<usize>,
383    /// Next sub-index to yield from `props[cols.start]`.
384    front_sub: usize,
385    /// One past the next sub-index to yield from `props[cols.end - 1]`.
386    back_sub: usize,
387    /// Names not yet yielded from either end.  Both ends decrement it, so it is
388    /// what stops them crossing while they share a column.
389    remaining: usize,
390}
391
392impl<'a, C: ColNames> PropNamesIter<'a, C> {
393    pub(crate) fn new(props: &'a [C]) -> Self {
394        Self {
395            props,
396            cols: 0..props.len(),
397            front_sub: 0,
398            back_sub: props.last().map_or(0, ColNames::name_count),
399            remaining: props.iter().map(ColNames::name_count).sum(),
400        }
401    }
402}
403
404impl<C: ColNames> Iterator for PropNamesIter<'_, C> {
405    type Item = C::Name;
406
407    fn next(&mut self) -> Option<C::Name> {
408        if self.remaining == 0 {
409            return None;
410        }
411        self.remaining -= 1;
412        loop {
413            // `remaining` was non-zero, so some column in `cols` still has a name.
414            let col = &self.props[self.cols.start];
415            if self.front_sub < col.name_count() {
416                let name = col.name_at(self.front_sub);
417                self.front_sub += 1;
418                return Some(name);
419            }
420            self.cols.start += 1;
421            self.front_sub = 0;
422        }
423    }
424
425    fn size_hint(&self) -> (usize, Option<usize>) {
426        (self.remaining, Some(self.remaining))
427    }
428}
429
430impl<C: ColNames> DoubleEndedIterator for PropNamesIter<'_, C> {
431    fn next_back(&mut self) -> Option<C::Name> {
432        if self.remaining == 0 {
433            return None;
434        }
435        self.remaining -= 1;
436        loop {
437            if self.back_sub > 0 {
438                self.back_sub -= 1;
439                return Some(self.props[self.cols.end - 1].name_at(self.back_sub));
440            }
441            self.cols.end -= 1;
442            self.back_sub = self.props[self.cols.end - 1].name_count();
443        }
444    }
445}
446
447impl<C: ColNames> ExactSizeIterator for PropNamesIter<'_, C> {
448    fn len(&self) -> usize {
449        self.remaining
450    }
451}
452
453impl<C: ColNames> FusedIterator for PropNamesIter<'_, C> {}
454
455/// A boxed per-column-slot value iterator yielding one `Option<`[`PropValueRef`]`>` per feature.
456type ColValIter<'l> = Box<dyn Iterator<Item = Option<PropValueRef<'l>>> + 'l>;
457
458/// Build one [`ColValIter`] per property column "slot" from a decoded column slice.
459///
460/// - Scalar and string columns contribute one slot each.
461/// - `SharedDict` columns contribute one slot per sub-item.
462fn build_col_iters<'p>(columns: &'p [ParsedProperty<'p>]) -> Vec<ColValIter<'p>> {
463    use ParsedProperty as PP;
464    let mut iters: Vec<ColValIter<'p>> = Vec::new();
465    for col in columns {
466        match col {
467            PP::Bool(s) => iters.push(scalar_col_iter(s)),
468            PP::I8(s) => iters.push(scalar_col_iter(s)),
469            PP::U8(s) => iters.push(scalar_col_iter(s)),
470            PP::I32(s) => iters.push(scalar_col_iter(s)),
471            PP::U32(s) => iters.push(scalar_col_iter(s)),
472            PP::I64(s) => iters.push(scalar_col_iter(s)),
473            PP::U64(s) => iters.push(scalar_col_iter(s)),
474            PP::F32(s) => iters.push(scalar_col_iter(s)),
475            PP::F64(s) => iters.push(scalar_col_iter(s)),
476            PP::Str(strings) => {
477                let data: &'p str = strings.data.as_ref();
478                let lengths: &'p [i32] = &strings.lengths;
479                let mut curr_end: usize = 0;
480                let mut feat_idx = 0usize;
481                iters.push(Box::new(std::iter::from_fn(move || {
482                    let &end_i32 = lengths.get(feat_idx)?;
483                    feat_idx += 1;
484                    if end_i32 >= 0 {
485                        let start = curr_end;
486                        curr_end = end_i32.cast_unsigned().into_usize();
487                        Some(data.get(start..curr_end).map(PropValueRef::Str))
488                    } else {
489                        // Null slot: curr_end unchanged (null encodes the current byte offset).
490                        Some(None)
491                    }
492                })));
493            }
494            PP::SharedDict(dict) => {
495                for item in &dict.items {
496                    let dict_ref: &'p _ = dict;
497                    let item_ref: &'p _ = item;
498                    let mut feat_idx = 0usize;
499                    iters.push(Box::new(std::iter::from_fn(move || {
500                        if feat_idx >= item_ref.ranges.len() {
501                            return None;
502                        }
503                        let idx = feat_idx;
504                        feat_idx += 1;
505                        Some(item_ref.get(dict_ref, idx).map(PropValueRef::Str))
506                    })));
507                }
508            }
509        }
510    }
511    iters
512}
513
514/// Build a boxed value iterator for a single scalar property column.
515fn scalar_col_iter<'p, T>(scalar: &'p ParsedScalar<'p, T>) -> ColValIter<'p>
516where
517    T: Copy + PartialEq,
518    PropValueRef<'p>: From<T>,
519{
520    Box::new(scalar.iter_optional().map(|o| o.map(PropValueRef::from)))
521}
522
523/// Iterator over the features of a fully-decoded [`Layer01<Parsed>`].
524///
525/// Returned by [`ParsedLayer01::iter_features`]. Implements [`LendingIterator`]:
526/// advance with `while let Some(feat) = iter.next()`.
527///
528/// Holds one O(1)-per-step cursor per property column slot. On each step the
529/// per-column cursors are advanced and their results written into a reused
530/// `values_buf` - yielding a [`FeatureRef`] that borrows that buffer with no
531/// per-feature heap allocation.
532pub struct Layer01FeatureIter<'layer, 'data: 'layer> {
533    layer: &'layer Layer01<'data, Parsed>,
534    index: usize,
535    feature_count: usize,
536    /// ID iterator, `None` when the layer has no ID column.
537    id_iter: Option<crate::utils::PresenceOptIter<'layer, u64>>,
538    /// One boxed value iterator per column slot (scalar, string, or `SharedDict` sub-item).
539    col_iters: Vec<ColValIter<'layer>>,
540    /// Reused buffer: filled on each `next()` call, borrowed by the yielded [`FeatureRef`].
541    values_buf: Vec<Option<PropValueRef<'layer>>>,
542}
543
544impl<'layer, 'data: 'layer> Layer01FeatureIter<'layer, 'data> {
545    fn new(layer: &'layer Layer01<'data, Parsed>) -> Self {
546        let col_iters = build_col_iters(&layer.properties);
547        let cap = col_iters.len();
548        Self {
549            layer,
550            index: 0,
551            feature_count: layer.feature_count(),
552            id_iter: layer.id.as_ref().map(|id| id.iter_optional()),
553            col_iters,
554            values_buf: Vec::with_capacity(cap),
555        }
556    }
557
558    /// Number of features not yet yielded.
559    #[must_use]
560    pub fn len(&self) -> usize {
561        self.feature_count - self.index
562    }
563
564    /// Returns `true` if all features have been yielded.
565    #[must_use]
566    pub fn is_empty(&self) -> bool {
567        self.index >= self.feature_count
568    }
569}
570
571impl<'layer> LendingIterator for Layer01FeatureIter<'layer, '_> {
572    type Item<'this>
573        = MltResult<FeatureRef<'this, 'layer>>
574    where
575        Self: 'this;
576
577    fn next(&mut self) -> Option<Self::Item<'_>> {
578        let index = self.index;
579        if index >= self.feature_count {
580            return None;
581        }
582        self.index += 1;
583
584        // Advance all per-feature cursors unconditionally, even if geometry decode fails,
585        // so that IDs and property values remain aligned with geometry indices.
586        let id = self.id_iter.as_mut().and_then(Iterator::next).flatten();
587        self.values_buf.clear();
588        self.values_buf
589            .extend(self.col_iters.iter_mut().map(|it| it.next().flatten()));
590
591        Some(
592            self.layer
593                .geometry
594                .to_geojson(index)
595                .map(|geometry| FeatureRef {
596                    id,
597                    geometry,
598                    columns: &self.layer.properties,
599                    values: &self.values_buf,
600                }),
601        )
602    }
603}
604
605#[cfg(test)]
606mod tests {
607    use geo_types::Point;
608    use serde_json::Value;
609
610    use super::*;
611    use crate::Layer;
612    use crate::decoder::GeometryValues;
613    use crate::encoder::model::StagedLayer;
614    use crate::encoder::{Codecs, Encoder, Presence, StagedId, StagedProperty, StagedSharedDict};
615    use crate::test_helpers::{assert_size_hint_exact, dec, parser};
616
617    fn layer_buf(staged: StagedLayer) -> Vec<u8> {
618        staged
619            .encode_into(Encoder::default(), &mut Codecs::default())
620            .unwrap()
621            .into_layer_bytes()
622            .unwrap()
623    }
624
625    fn three_points() -> GeometryValues {
626        let mut g = GeometryValues::default();
627        g.push_geom(&Geometry::<i32>::Point(Point::new(1, 2)));
628        g.push_geom(&Geometry::<i32>::Point(Point::new(3, 4)));
629        g.push_geom(&Geometry::<i32>::Point(Point::new(5, 6)));
630        g
631    }
632
633    fn empty_layer(name: &str) -> StagedLayer {
634        staged_layer(name, StagedId::None, GeometryValues::default(), vec![])
635    }
636
637    fn staged_layer(
638        name: &str,
639        id: StagedId,
640        geometry: GeometryValues,
641        properties: Vec<StagedProperty>,
642    ) -> StagedLayer {
643        StagedLayer::new(name, 4096, id, geometry, properties).unwrap()
644    }
645
646    #[test]
647    fn prop_name_display_concatenates_parts() {
648        assert_eq!(PropName("addr:", "city").to_string(), "addr:city");
649        assert_eq!(PropName("name", "").to_string(), "name");
650        assert_eq!(PropName("", "").to_string(), "");
651    }
652
653    #[test]
654    fn prop_name_eq_str_matches_concatenation() {
655        assert_eq!(PropName("addr:", "city"), "addr:city");
656        assert_eq!("addr:city", PropName("addr:", "city"));
657        assert_ne!(PropName("addr:", "city"), "addr:");
658        assert_ne!(PropName("addr:", "city"), "city");
659        assert_eq!(PropName("name", ""), "name");
660    }
661
662    #[test]
663    fn prop_name_structural_eq_is_part_wise() {
664        assert_eq!(PropName("a", "b"), PropName("a", "b"));
665        assert_eq!(PropName("ab", ""), PropName("a", "b"));
666    }
667
668    #[test]
669    fn prop_name_eq_prop_name_semantic_equality() {
670        assert_eq!(PropName("ab", ""), PropName("a", "b"));
671        assert_eq!(PropName("", "ab"), PropName("a", "b"));
672        assert_eq!(PropName("abc", "def"), PropName("ab", "cdef"));
673        assert_eq!(PropName("a", "bcdef"), PropName("abcde", "f"));
674
675        assert_ne!(PropName("a", "b"), PropName("a", "c"));
676        assert_ne!(PropName("a", "b"), PropName("ab", "c"));
677        assert_ne!(PropName("abc", ""), PropName("ab", ""));
678    }
679
680    #[test]
681    fn prop_value_ref_scalars_convert_to_json() {
682        assert_eq!(Value::from(PropValueRef::Bool(true)), Value::Bool(true));
683        assert_eq!(Value::from(PropValueRef::Bool(false)), Value::Bool(false));
684        assert_eq!(Value::from(PropValueRef::I8(-1)), Value::from(-1_i8));
685        assert_eq!(Value::from(PropValueRef::U8(255)), Value::from(255_u8));
686        assert_eq!(
687            Value::from(PropValueRef::I32(-1000)),
688            Value::from(-1000_i32)
689        );
690        assert_eq!(Value::from(PropValueRef::U32(1000)), Value::from(1000_u32));
691        assert_eq!(
692            Value::from(PropValueRef::I64(i64::MIN)),
693            Value::from(i64::MIN)
694        );
695        assert_eq!(
696            Value::from(PropValueRef::U64(u64::MAX)),
697            Value::from(u64::MAX)
698        );
699        assert_eq!(
700            Value::from(PropValueRef::Str("hello")),
701            Value::String("hello".into())
702        );
703    }
704
705    #[test]
706    fn prop_value_ref_float_finite_is_number() {
707        assert!(matches!(
708            Value::from(PropValueRef::F32(1.5)),
709            Value::Number(_)
710        ));
711        assert!(matches!(
712            Value::from(PropValueRef::F64(2.5)),
713            Value::Number(_)
714        ));
715    }
716
717    #[test]
718    fn prop_value_ref_float_non_finite_becomes_string_sentinel() {
719        assert_eq!(
720            Value::from(PropValueRef::F32(f32::NAN)),
721            Value::String("f32::NAN".into())
722        );
723        assert_eq!(
724            Value::from(PropValueRef::F32(f32::INFINITY)),
725            Value::String("f32::INFINITY".into())
726        );
727        assert_eq!(
728            Value::from(PropValueRef::F64(f64::NAN)),
729            Value::String("f64::NAN".into())
730        );
731        assert_eq!(
732            Value::from(PropValueRef::F64(f64::NEG_INFINITY)),
733            Value::String("f64::NEG_INFINITY".into())
734        );
735    }
736
737    #[test]
738    fn empty_layer_yields_no_features() {
739        let buf = layer_buf(empty_layer("empty"));
740        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
741        let Layer::Tag01(lazy) = layer else {
742            panic!("expected Tag01")
743        };
744        let parsed = lazy.decode_all(&mut dec()).unwrap();
745
746        let iter = parsed.iter_features();
747        assert_eq!(iter.len(), 0);
748        assert!(iter.is_empty());
749        assert_eq!(parsed.iter_features().len(), 0);
750    }
751
752    #[test]
753    fn len_decreases_with_each_next() {
754        let buf = layer_buf(staged_layer("test", StagedId::None, three_points(), vec![]));
755        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
756        let Layer::Tag01(lazy) = layer else { panic!() };
757        let parsed = lazy.decode_all(&mut dec()).unwrap();
758
759        let mut iter = parsed.iter_features();
760        assert_eq!(iter.len(), 3);
761        iter.next().unwrap().unwrap();
762        assert_eq!(iter.len(), 2);
763        iter.next().unwrap().unwrap();
764        assert_eq!(iter.len(), 1);
765        iter.next().unwrap().unwrap();
766        assert_eq!(iter.len(), 0);
767        assert!(iter.is_empty());
768        assert!(iter.next().is_none());
769    }
770
771    #[test]
772    fn feature_ids_are_preserved() {
773        let buf = layer_buf(staged_layer(
774            "test",
775            StagedId::from_optional(vec![Some(100), None, Some(200)]),
776            three_points(),
777            vec![],
778        ));
779        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
780        let Layer::Tag01(lazy) = layer else { panic!() };
781        let parsed = lazy.decode_all(&mut dec()).unwrap();
782
783        let mut ids = Vec::new();
784        let mut iter = parsed.iter_features();
785        while let Some(r) = iter.next() {
786            ids.push(r.unwrap().id);
787        }
788        assert_eq!(ids, [Some(100), None, Some(200)]);
789    }
790
791    #[test]
792    fn geometry_values_match_input() {
793        let buf = layer_buf(staged_layer("test", StagedId::None, three_points(), vec![]));
794        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
795        let Layer::Tag01(lazy) = layer else { panic!() };
796        let parsed = lazy.decode_all(&mut dec()).unwrap();
797
798        let mut geoms = Vec::new();
799        let mut iter = parsed.iter_features();
800        while let Some(r) = iter.next() {
801            geoms.push(r.unwrap().geometry);
802        }
803        assert_eq!(geoms[0], Geometry::<i32>::Point(Point::new(1, 2)));
804        assert_eq!(geoms[1], Geometry::<i32>::Point(Point::new(3, 4)));
805        assert_eq!(geoms[2], Geometry::<i32>::Point(Point::new(5, 6)));
806    }
807
808    #[test]
809    fn null_scalar_values_are_skipped() {
810        let buf = layer_buf(staged_layer(
811            "test",
812            StagedId::None,
813            three_points(),
814            vec![StagedProperty::opt_u32("n", vec![Some(1), None, Some(3)])],
815        ));
816        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
817        let Layer::Tag01(lazy) = layer else { panic!() };
818        let parsed = lazy.decode_all(&mut dec()).unwrap();
819
820        let mut iter = parsed.iter_features();
821
822        {
823            let feat = iter.next().unwrap().unwrap();
824            let cols: Vec<_> = feat.iter_properties().collect();
825            assert_eq!(cols.len(), 1);
826            assert_eq!(cols[0].name, PropName("n", ""));
827            assert_eq!(cols[0].name, "n");
828            assert_eq!(cols[0].value, PropValueRef::U32(1));
829            let all: Vec<_> = feat.iter_all_properties().collect();
830            assert_eq!(all, [Some(PropValueRef::U32(1))]);
831        }
832        {
833            let feat = iter.next().unwrap().unwrap();
834            assert!(feat.iter_properties().next().is_none());
835            let all: Vec<_> = feat.iter_all_properties().collect();
836            assert_eq!(all, [None]);
837        }
838        {
839            let feat = iter.next().unwrap().unwrap();
840            assert_eq!(feat.get_property("n"), Some(PropValueRef::U32(3)));
841            let all: Vec<_> = feat.iter_all_properties().collect();
842            assert_eq!(all, [Some(PropValueRef::U32(3))]);
843        }
844
845        let names: Vec<_> = parsed.iterate_prop_names().map(|n| n.to_string()).collect();
846        assert_eq!(names, ["n"]);
847    }
848
849    #[test]
850    fn null_string_values_are_skipped() {
851        let buf = layer_buf(staged_layer(
852            "test",
853            StagedId::None,
854            three_points(),
855            vec![StagedProperty::opt_str(
856                "label",
857                vec![Some("foo"), None, Some("bar")],
858            )],
859        ));
860        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
861        let Layer::Tag01(lazy) = layer else { panic!() };
862        let parsed = lazy.decode_all(&mut dec()).unwrap();
863
864        let mut iter = parsed.iter_features();
865        {
866            let feat = iter.next().unwrap().unwrap();
867            assert_eq!(feat.get_property("label"), Some(PropValueRef::Str("foo")));
868        }
869        {
870            let feat = iter.next().unwrap().unwrap();
871            assert_eq!(feat.get_property("label"), None);
872        }
873        {
874            let feat = iter.next().unwrap().unwrap();
875            assert_eq!(feat.get_property("label"), Some(PropValueRef::Str("bar")));
876        }
877    }
878
879    #[test]
880    fn multiple_columns_independently_nullable() {
881        let buf = layer_buf(staged_layer(
882            "test",
883            StagedId::None,
884            three_points(),
885            vec![
886                StagedProperty::opt_bool("flag", vec![Some(true), Some(false), None]),
887                StagedProperty::opt_i32("score", vec![None, Some(-5), Some(7)]),
888            ],
889        ));
890        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
891        let Layer::Tag01(lazy) = layer else { panic!() };
892        let parsed = lazy.decode_all(&mut dec()).unwrap();
893
894        let mut iter = parsed.iter_features();
895
896        // feat 0: flag=true, score=null -> 1 property
897        {
898            let feat = iter.next().unwrap().unwrap();
899            assert_eq!(feat.iter_properties().count(), 1);
900            assert_eq!(feat.get_property("flag"), Some(PropValueRef::Bool(true)));
901            assert_eq!(feat.get_property("score"), None);
902        }
903        // feat 1: flag=false, score=-5 -> 2 properties
904        {
905            let feat = iter.next().unwrap().unwrap();
906            assert_eq!(feat.iter_properties().count(), 2);
907            assert_eq!(feat.get_property("flag"), Some(PropValueRef::Bool(false)));
908            assert_eq!(feat.get_property("score"), Some(PropValueRef::I32(-5)));
909        }
910        // feat 2: flag=null, score=7 -> 1 property
911        {
912            let feat = iter.next().unwrap().unwrap();
913            assert_eq!(feat.iter_properties().count(), 1);
914            assert_eq!(feat.get_property("flag"), None);
915            assert_eq!(feat.get_property("score"), Some(PropValueRef::I32(7)));
916        }
917    }
918
919    #[test]
920    fn geometry_error_does_not_misalign_ids() {
921        use crate::decoder::GeometryType;
922
923        let buf = layer_buf(staged_layer(
924            "test",
925            StagedId::from_optional(vec![Some(10), Some(20), Some(30)]),
926            three_points(),
927            vec![],
928        ));
929        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
930        let Layer::Tag01(lazy) = layer else { panic!() };
931        let mut parsed = lazy.decode_all(&mut dec()).unwrap();
932
933        // Corrupt feature 1's geometry type: Point -> LineString.
934        // A LineString requires part_offsets, which are absent here, so
935        // to_geojson(1) will return Err(NoPartOffsets).
936        parsed.geometry.vector_types[1] = GeometryType::LineString;
937
938        let mut iter = parsed.iter_features();
939
940        // Feature 0: valid Point, id = Some(10)
941        let feat0 = iter.next().unwrap().unwrap();
942        assert_eq!(feat0.id, Some(10));
943
944        // Feature 1: geometry error - iterator still advances ID cursor
945        assert!(iter.next().unwrap().is_err());
946
947        // Feature 2: valid Point, id must be Some(30), not Some(20)
948        let feat2 = iter.next().unwrap().unwrap();
949        assert_eq!(
950            feat2.id,
951            Some(30),
952            "id cursor was not advanced on geometry error"
953        );
954
955        assert!(iter.next().is_none());
956    }
957
958    #[test]
959    fn get_property_absent_column_returns_none() {
960        let buf = layer_buf(staged_layer(
961            "test",
962            StagedId::None,
963            three_points(),
964            vec![StagedProperty::u32("x", vec![1, 2, 3])],
965        ));
966        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
967        let Layer::Tag01(lazy) = layer else { panic!() };
968        let parsed = lazy.decode_all(&mut dec()).unwrap();
969
970        let mut iter = parsed.iter_features();
971        let feat = iter.next().unwrap().unwrap();
972        assert_eq!(feat.get_property("no_such_column"), None);
973    }
974
975    #[test]
976    fn shared_dict_columns_are_expanded() {
977        let shared_dict = StagedSharedDict::new(
978            "addr:",
979            [
980                (
981                    "city",
982                    vec![Some("Paris"), Some("Rome"), None],
983                    Presence::Mixed,
984                ),
985                (
986                    "zip",
987                    vec![Some("75001"), None, Some("00100")],
988                    Presence::Mixed,
989                ),
990            ],
991        )
992        .unwrap();
993
994        let buf = layer_buf(staged_layer(
995            "test",
996            StagedId::None,
997            three_points(),
998            vec![StagedProperty::SharedDict(shared_dict)],
999        ));
1000        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
1001        let Layer::Tag01(lazy) = layer else { panic!() };
1002        let parsed = lazy.decode_all(&mut dec()).unwrap();
1003
1004        let mut iter = parsed.iter_features();
1005
1006        // feat 0: city=Paris, zip=75001
1007        {
1008            let feat = iter.next().unwrap().unwrap();
1009            assert_eq!(
1010                feat.get_property("addr:city"),
1011                Some(PropValueRef::Str("Paris"))
1012            );
1013            assert_eq!(
1014                feat.get_property("addr:zip"),
1015                Some(PropValueRef::Str("75001"))
1016            );
1017            assert_eq!(feat.iter_properties().count(), 2);
1018        }
1019        // feat 1: city=Rome, zip=null
1020        {
1021            let feat = iter.next().unwrap().unwrap();
1022            assert_eq!(
1023                feat.get_property("addr:city"),
1024                Some(PropValueRef::Str("Rome"))
1025            );
1026            assert_eq!(feat.get_property("addr:zip"), None);
1027            assert_eq!(feat.iter_properties().count(), 1);
1028            // iter_all_properties: values only (no names); SharedDict expands to two slots
1029            let all: Vec<_> = feat.iter_all_properties().collect();
1030            assert_eq!(all, [Some(PropValueRef::Str("Rome")), None]);
1031        }
1032        // feat 2: city=null, zip=00100
1033        {
1034            let feat = iter.next().unwrap().unwrap();
1035            assert_eq!(feat.get_property("addr:city"), None);
1036            assert_eq!(
1037                feat.get_property("addr:zip"),
1038                Some(PropValueRef::Str("00100"))
1039            );
1040        }
1041
1042        let names: Vec<_> = parsed.iterate_prop_names().map(|n| n.to_string()).collect();
1043        assert_eq!(names, ["addr:city", "addr:zip"]);
1044    }
1045
1046    fn strs<'p>(iter: impl Iterator<Item = PropName<'p>>) -> Vec<String> {
1047        iter.map(|n| n.to_string()).collect()
1048    }
1049
1050    fn reversed<'p>(iter: impl DoubleEndedIterator<Item = PropName<'p>>) -> Vec<String> {
1051        let mut names = strs(iter.rev());
1052        names.reverse();
1053        names
1054    }
1055
1056    fn both_ends_layer() -> Vec<u8> {
1057        let shared_dict = StagedSharedDict::new(
1058            "addr:",
1059            [
1060                ("city", vec![Some("Paris"); 3], Presence::AllPresent),
1061                ("zip", vec![Some("75001"); 3], Presence::AllPresent),
1062                ("street", vec![Some("Rue"); 3], Presence::AllPresent),
1063            ],
1064        )
1065        .unwrap();
1066
1067        layer_buf(staged_layer(
1068            "test",
1069            StagedId::None,
1070            three_points(),
1071            vec![
1072                StagedProperty::str("before", ["a", "a", "a"]),
1073                StagedProperty::u32("count", vec![1, 2, 3]),
1074                StagedProperty::SharedDict(shared_dict),
1075                StagedProperty::str("after", ["b", "b", "b"]),
1076            ],
1077        ))
1078    }
1079
1080    const BOTH_ENDS_NAMES: [&str; 6] = [
1081        "before",
1082        "count",
1083        "addr:city",
1084        "addr:zip",
1085        "addr:street",
1086        "after",
1087    ];
1088
1089    fn prop_names() -> Vec<PropName<'static>> {
1090        BOTH_ENDS_NAMES.iter().map(|n| PropName(n, "")).collect()
1091    }
1092
1093    #[test]
1094    fn prop_names_iterate_from_both_ends_lazy() {
1095        let buf = both_ends_layer();
1096        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
1097        let Layer::Tag01(lazy) = layer else { panic!() };
1098
1099        assert_eq!(strs(lazy.iterate_prop_names()), BOTH_ENDS_NAMES);
1100        assert_eq!(reversed(lazy.iterate_prop_names()), BOTH_ENDS_NAMES);
1101        assert_size_hint_exact(|| lazy.iterate_prop_names(), &prop_names());
1102    }
1103
1104    #[test]
1105    fn prop_names_iterate_from_both_ends_parsed() {
1106        let buf = both_ends_layer();
1107        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
1108        let Layer::Tag01(lazy) = layer else { panic!() };
1109        let parsed = lazy.decode_all(&mut dec()).unwrap();
1110
1111        assert_eq!(strs(parsed.iterate_prop_names()), BOTH_ENDS_NAMES);
1112        assert_eq!(reversed(parsed.iterate_prop_names()), BOTH_ENDS_NAMES);
1113
1114        assert_size_hint_exact(|| parsed.iterate_prop_names(), &prop_names());
1115    }
1116
1117    #[test]
1118    fn prop_names_of_layer_without_properties() {
1119        let buf = layer_buf(empty_layer("test"));
1120        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
1121        let Layer::Tag01(lazy) = layer else { panic!() };
1122
1123        assert_eq!(lazy.iterate_prop_names().size_hint(), (0, Some(0)));
1124        assert_eq!(lazy.iterate_prop_names().next(), None);
1125        assert_eq!(lazy.iterate_prop_names().next_back(), None);
1126
1127        let parsed = lazy.decode_all(&mut dec()).unwrap();
1128        assert_eq!(parsed.iterate_prop_names().size_hint(), (0, Some(0)));
1129        assert_eq!(parsed.iterate_prop_names().next(), None);
1130        assert_eq!(parsed.iterate_prop_names().next_back(), None);
1131    }
1132
1133    /// `Layer01<Lazy>` only ever holds `Raw` columns today, so the `Parsed` and
1134    /// `ParsingFailed` arms are exercised against a hand-built column slice.
1135    #[test]
1136    fn prop_names_skip_columns_that_failed_to_parse() {
1137        let buf = both_ends_layer();
1138        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
1139        let Layer::Tag01(lazy) = layer else { panic!() };
1140        let raw = lazy.properties.clone();
1141
1142        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
1143        let Layer::Tag01(lazy) = layer else { panic!() };
1144        let parsed = lazy.decode_all(&mut dec()).unwrap().properties;
1145
1146        let mixed = vec![
1147            raw[0].clone(),
1148            LazyParsed::ParsingFailed,
1149            LazyParsed::Parsed(parsed[2].clone()),
1150            LazyParsed::ParsingFailed,
1151            raw[3].clone(),
1152        ];
1153        let expected = ["before", "addr:city", "addr:zip", "addr:street", "after"];
1154
1155        assert_eq!(strs(PropNamesIter::new(&mixed)), expected);
1156        assert_eq!(reversed(PropNamesIter::new(&mixed)), expected);
1157        assert_size_hint_exact(
1158            || PropNamesIter::new(&mixed),
1159            &expected.map(|n| PropName(n, "")),
1160        );
1161
1162        let all_failed = vec![LazyParsed::ParsingFailed; 3];
1163        assert_eq!(PropNamesIter::new(&all_failed).size_hint(), (0, Some(0)));
1164        assert_eq!(PropNamesIter::new(&all_failed).next(), None);
1165        assert_eq!(PropNamesIter::new(&all_failed).next_back(), None);
1166    }
1167
1168    #[test]
1169    fn feature_property_iterators_run_backwards() {
1170        let buf = both_ends_layer();
1171        let (_, layer) = Layer::from_bytes(&buf, &mut parser()).unwrap();
1172        let Layer::Tag01(lazy) = layer else { panic!() };
1173        let parsed = lazy.decode_all(&mut dec()).unwrap();
1174
1175        let mut iter = parsed.iter_features();
1176        let feat = iter.next().unwrap().unwrap();
1177
1178        let all: Vec<_> = feat.iter_all_properties().collect();
1179        assert_size_hint_exact(|| feat.iter_all_properties(), &all);
1180        let mut all_back: Vec<_> = feat.iter_all_properties().rev().collect();
1181        all_back.reverse();
1182        assert_eq!(all_back, all);
1183
1184        let mut props_back: Vec<_> = feat
1185            .iter_properties()
1186            .rev()
1187            .map(|c| c.name().to_string())
1188            .collect();
1189        props_back.reverse();
1190        assert_eq!(props_back, BOTH_ENDS_NAMES);
1191    }
1192}