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