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