Skip to main content

iceberg/spec/name_mapping/
mod.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Iceberg name mapping.
19
20use std::str::FromStr;
21use std::sync::Arc;
22
23use serde::{Deserialize, Serialize};
24use serde_with::{DefaultOnNull, serde_as};
25
26use crate::{Error, ErrorKind, Result};
27
28/// Iceberg fallback field name to ID mapping.
29#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
30#[serde(transparent)]
31pub struct NameMapping {
32    root: Vec<MappedField>,
33}
34
35impl NameMapping {
36    /// Create a new [`NameMapping`] given a collection of mapped fields.
37    pub fn new(fields: Vec<MappedField>) -> Self {
38        Self { root: fields }
39    }
40
41    /// Get a reference to fields which are to be mapped from name to field ID.
42    pub fn fields(&self) -> &[MappedField] {
43        &self.root
44    }
45}
46
47impl FromStr for NameMapping {
48    type Err = Error;
49
50    /// Parses a [`NameMapping`] from its JSON representation.
51    fn from_str(value: &str) -> Result<Self> {
52        serde_json::from_str(value).map_err(|error| {
53            Error::new(
54                ErrorKind::DataInvalid,
55                "Failed to parse value as a NameMapping",
56            )
57            .with_source(error)
58        })
59    }
60}
61
62/// Maps field names to IDs.
63#[serde_as]
64#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
65#[serde(rename_all = "kebab-case")]
66pub struct MappedField {
67    #[serde(skip_serializing_if = "Option::is_none")]
68    field_id: Option<i32>,
69    names: Vec<String>,
70    #[serde(default)]
71    #[serde(skip_serializing_if = "Vec::is_empty")]
72    #[serde_as(deserialize_as = "DefaultOnNull")]
73    fields: Vec<Arc<MappedField>>,
74}
75
76impl MappedField {
77    /// Create a new [`MappedField`].
78    pub fn new(field_id: Option<i32>, names: Vec<String>, fields: Vec<MappedField>) -> Self {
79        Self {
80            field_id,
81            names,
82            fields: fields.into_iter().map(Arc::new).collect(),
83        }
84    }
85
86    /// Iceberg field ID when a field's name is present within `names`.
87    pub fn field_id(&self) -> Option<i32> {
88        self.field_id
89    }
90
91    /// Get a reference to names for a mapped field.
92    pub fn names(&self) -> &[String] {
93        &self.names
94    }
95
96    /// Get a reference to the field mapping for any child fields.
97    pub fn fields(&self) -> &[Arc<MappedField>] {
98        &self.fields
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn test_json_mapped_field_deserialization() {
108        let expected = MappedField {
109            field_id: Some(1),
110            names: vec!["id".to_string(), "record_id".to_string()],
111            fields: vec![],
112        };
113        let mapped_field = r#"
114        {
115            "field-id": 1,
116            "names": ["id", "record_id"]
117        }
118        "#;
119
120        let mapped_field: MappedField = serde_json::from_str(mapped_field).unwrap();
121        assert_eq!(mapped_field, expected);
122
123        let mapped_field_with_null_fields = r#"
124        {
125            "field-id": 1,
126            "names": ["id", "record_id"],
127            "fields": null
128        }
129        "#;
130
131        let mapped_field_with_null_fields: MappedField =
132            serde_json::from_str(mapped_field_with_null_fields).unwrap();
133        assert_eq!(mapped_field_with_null_fields, expected);
134    }
135
136    #[test]
137    fn test_json_mapped_field_no_names_deserialization() {
138        let expected = MappedField {
139            field_id: Some(1),
140            names: vec![],
141            fields: vec![],
142        };
143        let mapped_field = r#"
144        {
145            "field-id": 1,
146            "names": []
147        }
148        "#;
149
150        let mapped_field: MappedField = serde_json::from_str(mapped_field).unwrap();
151        assert_eq!(mapped_field, expected);
152
153        let mapped_field_with_null_fields = r#"
154        {
155            "field-id": 1,
156            "names": [],
157            "fields": null
158        }
159        "#;
160
161        let mapped_field_with_null_fields: MappedField =
162            serde_json::from_str(mapped_field_with_null_fields).unwrap();
163        assert_eq!(mapped_field_with_null_fields, expected);
164    }
165
166    #[test]
167    fn test_json_mapped_field_no_field_id_deserialization() {
168        let expected = MappedField {
169            field_id: None,
170            names: vec!["id".to_string(), "record_id".to_string()],
171            fields: vec![],
172        };
173        let mapped_field = r#"
174        {
175            "names": ["id", "record_id"]
176        }
177        "#;
178
179        let mapped_field: MappedField = serde_json::from_str(mapped_field).unwrap();
180        assert_eq!(mapped_field, expected);
181
182        let mapped_field_with_null_fields = r#"
183        {
184            "names": ["id", "record_id"],
185            "fields": null
186        }
187        "#;
188
189        let mapped_field_with_null_fields: MappedField =
190            serde_json::from_str(mapped_field_with_null_fields).unwrap();
191        assert_eq!(mapped_field_with_null_fields, expected);
192    }
193
194    #[test]
195    fn test_json_name_mapping_deserialization() {
196        let name_mapping = r#"
197        [
198            {
199                "field-id": 1,
200                "names": [
201                    "id",
202                    "record_id"
203                ]
204            },
205            {
206                "field-id": 2,
207                "names": [
208                    "data"
209                ]
210            },
211            {
212                "field-id": 3,
213                "names": [
214                    "location"
215                ],
216                "fields": [
217                    {
218                        "field-id": 4,
219                        "names": [
220                            "latitude",
221                            "lat"
222                        ]
223                    },
224                    {
225                        "field-id": 5,
226                        "names": [
227                            "longitude",
228                            "long"
229                        ]
230                    }
231                ]
232            }
233        ]
234        "#;
235
236        let name_mapping: NameMapping = serde_json::from_str(name_mapping).unwrap();
237        assert_eq!(name_mapping, NameMapping {
238            root: vec![
239                MappedField {
240                    field_id: Some(1),
241                    names: vec!["id".to_string(), "record_id".to_string()],
242                    fields: vec![]
243                },
244                MappedField {
245                    field_id: Some(2),
246                    names: vec!["data".to_string()],
247                    fields: vec![]
248                },
249                MappedField {
250                    field_id: Some(3),
251                    names: vec!["location".to_string()],
252                    fields: vec![
253                        MappedField {
254                            field_id: Some(4),
255                            names: vec!["latitude".to_string(), "lat".to_string()],
256                            fields: vec![]
257                        }
258                        .into(),
259                        MappedField {
260                            field_id: Some(5),
261                            names: vec!["longitude".to_string(), "long".to_string()],
262                            fields: vec![]
263                        }
264                        .into(),
265                    ]
266                }
267            ],
268        });
269    }
270
271    #[test]
272    fn test_json_name_mapping_serialization() {
273        let name_mapping = NameMapping {
274            root: vec![
275                MappedField {
276                    field_id: None,
277                    names: vec!["foo".to_string()],
278                    fields: vec![],
279                },
280                MappedField {
281                    field_id: Some(2),
282                    names: vec!["bar".to_string()],
283                    fields: vec![],
284                },
285                MappedField {
286                    field_id: Some(3),
287                    names: vec!["baz".to_string()],
288                    fields: vec![],
289                },
290                MappedField {
291                    field_id: Some(4),
292                    names: vec!["qux".to_string()],
293                    fields: vec![
294                        MappedField {
295                            field_id: Some(5),
296                            names: vec!["element".to_string()],
297                            fields: vec![],
298                        }
299                        .into(),
300                    ],
301                },
302                MappedField {
303                    field_id: Some(6),
304                    names: vec!["quux".to_string()],
305                    fields: vec![
306                        MappedField {
307                            field_id: Some(7),
308                            names: vec!["key".to_string()],
309                            fields: vec![],
310                        }
311                        .into(),
312                        MappedField {
313                            field_id: Some(8),
314                            names: vec!["value".to_string()],
315                            fields: vec![
316                                MappedField {
317                                    field_id: Some(9),
318                                    names: vec!["key".to_string()],
319                                    fields: vec![],
320                                }
321                                .into(),
322                                MappedField {
323                                    field_id: Some(10),
324                                    names: vec!["value".to_string()],
325                                    fields: vec![],
326                                }
327                                .into(),
328                            ],
329                        }
330                        .into(),
331                    ],
332                },
333                MappedField {
334                    field_id: Some(11),
335                    names: vec!["location".to_string()],
336                    fields: vec![
337                        MappedField {
338                            field_id: Some(12),
339                            names: vec!["element".to_string()],
340                            fields: vec![
341                                MappedField {
342                                    field_id: Some(13),
343                                    names: vec!["latitude".to_string()],
344                                    fields: vec![],
345                                }
346                                .into(),
347                                MappedField {
348                                    field_id: Some(14),
349                                    names: vec!["longitude".to_string()],
350                                    fields: vec![],
351                                }
352                                .into(),
353                            ],
354                        }
355                        .into(),
356                    ],
357                },
358                MappedField {
359                    field_id: Some(15),
360                    names: vec!["person".to_string()],
361                    fields: vec![
362                        MappedField {
363                            field_id: Some(16),
364                            names: vec!["name".to_string()],
365                            fields: vec![],
366                        }
367                        .into(),
368                        MappedField {
369                            field_id: Some(17),
370                            names: vec!["age".to_string()],
371                            fields: vec![],
372                        }
373                        .into(),
374                    ],
375                },
376            ],
377        };
378        let expected = r#"[{"names":["foo"]},{"field-id":2,"names":["bar"]},{"field-id":3,"names":["baz"]},{"field-id":4,"names":["qux"],"fields":[{"field-id":5,"names":["element"]}]},{"field-id":6,"names":["quux"],"fields":[{"field-id":7,"names":["key"]},{"field-id":8,"names":["value"],"fields":[{"field-id":9,"names":["key"]},{"field-id":10,"names":["value"]}]}]},{"field-id":11,"names":["location"],"fields":[{"field-id":12,"names":["element"],"fields":[{"field-id":13,"names":["latitude"]},{"field-id":14,"names":["longitude"]}]}]},{"field-id":15,"names":["person"],"fields":[{"field-id":16,"names":["name"]},{"field-id":17,"names":["age"]}]}]"#;
379        assert_eq!(serde_json::to_string(&name_mapping).unwrap(), expected);
380    }
381}