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