Skip to main content

mlt_core/convert/
infer.rs

1//! Column kind inference shared by the importers of dynamically typed
2//! properties (MVT, `GeoJSON`), which need one fixed [`PropKind`] per column.
3
4use std::collections::HashMap;
5
6use crate::tile::PropKind;
7
8/// Column kind inferred so far. `Unknown` is a column seen only as null, which
9/// resolves to [`PropKind::Str`].
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum InferredKind {
12    Unknown,
13    Bool,
14    I64,
15    U64,
16    F32,
17    F64,
18    Str,
19}
20
21impl InferredKind {
22    /// Merge with another kind: `I64`+`U64` widen to `I64`, floats widen to
23    /// `F64`, integers mixed with floats widen to `F64`, and any other conflict
24    /// falls back to `Str`.
25    #[must_use]
26    pub fn merge(self, other: Self) -> Self {
27        if self == Self::Unknown {
28            return other;
29        }
30        if other == Self::Unknown || self == other {
31            return self;
32        }
33        self.prop_kind().merge(other.prop_kind()).into()
34    }
35
36    #[must_use]
37    pub fn prop_kind(self) -> PropKind {
38        match self {
39            Self::Unknown | Self::Str => PropKind::Str,
40            Self::Bool => PropKind::Bool,
41            Self::I64 => PropKind::I64,
42            Self::U64 => PropKind::U64,
43            Self::F32 => PropKind::F32,
44            Self::F64 => PropKind::F64,
45        }
46    }
47}
48
49impl From<PropKind> for InferredKind {
50    fn from(kind: PropKind) -> Self {
51        match kind {
52            PropKind::Bool => Self::Bool,
53            PropKind::I64 => Self::I64,
54            PropKind::U64 => Self::U64,
55            PropKind::F32 => Self::F32,
56            PropKind::F64 => Self::F64,
57            PropKind::I8 | PropKind::U8 | PropKind::I32 | PropKind::U32 | PropKind::Str => {
58                Self::Str
59            }
60        }
61    }
62}
63
64/// Column names in first-seen order, each with the kind merged from every value seen.
65#[derive(Default)]
66pub(crate) struct ColumnInference<'a> {
67    index: HashMap<&'a str, usize>,
68    names: Vec<String>,
69    kinds: Vec<InferredKind>,
70}
71
72impl<'a> ColumnInference<'a> {
73    /// Record a value of `kind` in column `name`, returning the column's index.
74    pub fn observe(&mut self, name: &'a str, kind: InferredKind) -> usize {
75        let idx = self.column(name);
76        self.merge(idx, kind);
77        idx
78    }
79
80    /// The index of column `name`, adding it on first sight.
81    pub fn column(&mut self, name: &'a str) -> usize {
82        *self.index.entry(name).or_insert_with(|| {
83            self.names.push(name.to_string());
84            self.kinds.push(InferredKind::Unknown);
85            self.names.len() - 1
86        })
87    }
88
89    /// Record a value of `kind` in the column at `idx`.
90    pub fn merge(&mut self, idx: usize, kind: InferredKind) {
91        // One bounds check rather than one per index expression.
92        let slot = &mut self.kinds[idx];
93        *slot = slot.merge(kind);
94    }
95
96    /// The column names and their resolved kinds, in first-seen order.
97    pub fn finish(self) -> (Vec<String>, Vec<PropKind>) {
98        let kinds = self
99            .kinds
100            .into_iter()
101            .map(InferredKind::prop_kind)
102            .collect();
103        (self.names, kinds)
104    }
105}