arrow_schema/
datatype_display.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
18use std::{collections::HashMap, fmt};
19
20use crate::DataType;
21
22impl fmt::Display for DataType {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        fn format_metadata(metadata: &HashMap<String, String>) -> String {
25            if metadata.is_empty() {
26                String::new()
27            } else {
28                format!(", metadata: {metadata:?}")
29            }
30        }
31
32        // A lot of these can still be improved a lot.
33        // _Some_ of these can be parsed with `FromStr`, but not all (YET!).
34        // The goal is that the formatting should always be
35        // * Terse and teadable
36        // * Reversible (contain all necessary information to reverse it perfectly)
37
38        match &self {
39            Self::Null => write!(f, "Null"),
40            Self::Boolean => write!(f, "Boolean"),
41            Self::Int8 => write!(f, "Int8"),
42            Self::Int16 => write!(f, "Int16"),
43            Self::Int32 => write!(f, "Int32"),
44            Self::Int64 => write!(f, "Int64"),
45            Self::UInt8 => write!(f, "UInt8"),
46            Self::UInt16 => write!(f, "UInt16"),
47            Self::UInt32 => write!(f, "UInt32"),
48            Self::UInt64 => write!(f, "UInt64"),
49            Self::Float16 => write!(f, "Float16"),
50            Self::Float32 => write!(f, "Float32"),
51            Self::Float64 => write!(f, "Float64"),
52            Self::Timestamp(time_unit, timezone) => {
53                if let Some(timezone) = timezone {
54                    write!(f, "Timestamp({time_unit}, {timezone:?})")
55                } else {
56                    write!(f, "Timestamp({time_unit})")
57                }
58            }
59            Self::Date32 => write!(f, "Date32"),
60            Self::Date64 => write!(f, "Date64"),
61            Self::Time32(time_unit) => write!(f, "Time32({time_unit})"),
62            Self::Time64(time_unit) => write!(f, "Time64({time_unit})"),
63            Self::Duration(time_unit) => write!(f, "Duration({time_unit})"),
64            Self::Interval(interval_unit) => write!(f, "Interval({interval_unit:?})"),
65            Self::Binary => write!(f, "Binary"),
66            Self::FixedSizeBinary(bytes_per_value) => {
67                write!(f, "FixedSizeBinary({bytes_per_value:?})")
68            }
69            Self::LargeBinary => write!(f, "LargeBinary"),
70            Self::BinaryView => write!(f, "BinaryView"),
71            Self::Utf8 => write!(f, "Utf8"),
72            Self::LargeUtf8 => write!(f, "LargeUtf8"),
73            Self::Utf8View => write!(f, "Utf8View"),
74            Self::ListView(field) => write!(f, "ListView({field})"), // TODO: make more readable
75            Self::LargeListView(field) => write!(f, "LargeListView({field})"), // TODO: make more readable
76            Self::List(field) | Self::LargeList(field) => {
77                let type_name = if matches!(self, Self::List(_)) {
78                    "List"
79                } else {
80                    "LargeList"
81                };
82
83                let name = field.name();
84                let maybe_nullable = if field.is_nullable() { "nullable " } else { "" };
85                let data_type = field.data_type();
86                let field_name_str = if name == "item" {
87                    String::default()
88                } else {
89                    format!(", field: '{name}'")
90                };
91                let metadata_str = format_metadata(field.metadata());
92
93                // e.g. `LargeList(nullable Uint32)
94                write!(
95                    f,
96                    "{type_name}({maybe_nullable}{data_type}{field_name_str}{metadata_str})"
97                )
98            }
99            Self::FixedSizeList(field, size) => {
100                let name = field.name();
101                let maybe_nullable = if field.is_nullable() { "nullable " } else { "" };
102                let data_type = field.data_type();
103                let field_name_str = if name == "item" {
104                    String::default()
105                } else {
106                    format!(", field: '{name}'")
107                };
108                let metadata_str = format_metadata(field.metadata());
109
110                write!(
111                    f,
112                    "FixedSizeList({size} x {maybe_nullable}{data_type}{field_name_str}{metadata_str})",
113                )
114            }
115            Self::Struct(fields) => {
116                write!(f, "Struct(")?;
117                if !fields.is_empty() {
118                    let fields_str = fields
119                        .iter()
120                        .map(|field| {
121                            let name = field.name();
122                            let maybe_nullable = if field.is_nullable() { "nullable " } else { "" };
123                            let data_type = field.data_type();
124                            let metadata_str = format_metadata(field.metadata());
125                            format!("{name:?}: {maybe_nullable}{data_type}{metadata_str}")
126                        })
127                        .collect::<Vec<_>>()
128                        .join(", ");
129                    write!(f, "{fields_str}")?;
130                }
131                write!(f, ")")?;
132                Ok(())
133            }
134            Self::Union(union_fields, union_mode) => {
135                write!(f, "Union({union_fields:?}, {union_mode:?})")
136            }
137            Self::Dictionary(data_type, data_type1) => {
138                write!(f, "Dictionary({data_type}, {data_type1})")
139            }
140            Self::Decimal32(precision, scale) => write!(f, "Decimal32({precision}, {scale})"),
141            Self::Decimal64(precision, scale) => write!(f, "Decimal64({precision}, {scale})"),
142            Self::Decimal128(precision, scale) => write!(f, "Decimal128({precision}, {scale})"),
143            Self::Decimal256(precision, scale) => write!(f, "Decimal256({precision}, {scale})"),
144            Self::Map(field, keys_are_sorted) => write!(f, "Map({field}, {keys_are_sorted})"),
145            Self::RunEndEncoded(run_ends_field, values_field) => {
146                write!(f, "RunEndEncoded({run_ends_field}, {values_field})")
147            }
148        }
149    }
150}
151
152#[cfg(test)]
153mod tests {
154
155    use std::sync::Arc;
156
157    use crate::Field;
158
159    use super::*;
160
161    #[test]
162    fn test_display_list() {
163        let list_data_type = DataType::List(Arc::new(Field::new_list_field(DataType::Int32, true)));
164        let list_data_type_string = list_data_type.to_string();
165        let expected_string = "List(nullable Int32)";
166        assert_eq!(list_data_type_string, expected_string);
167    }
168
169    #[test]
170    fn test_display_list_with_named_field() {
171        let list_data_type = DataType::List(Arc::new(Field::new("foo", DataType::UInt64, false)));
172        let list_data_type_string = list_data_type.to_string();
173        let expected_string = "List(UInt64, field: 'foo')";
174        assert_eq!(list_data_type_string, expected_string);
175    }
176
177    #[test]
178    fn test_display_nested_list() {
179        let nested_data_type = DataType::List(Arc::new(Field::new_list_field(
180            DataType::List(Arc::new(Field::new_list_field(DataType::UInt64, false))),
181            false,
182        )));
183        let nested_data_type_string = nested_data_type.to_string();
184        let nested_expected_string = "List(List(UInt64))";
185        assert_eq!(nested_data_type_string, nested_expected_string);
186    }
187
188    #[test]
189    fn test_display_list_with_metadata() {
190        let mut field = Field::new_list_field(DataType::Int32, true);
191        let metadata = HashMap::from([("foo1".to_string(), "value1".to_string())]);
192        field.set_metadata(metadata);
193        let list_data_type = DataType::List(Arc::new(field));
194        let list_data_type_string = list_data_type.to_string();
195        let expected_string = "List(nullable Int32, metadata: {\"foo1\": \"value1\"})";
196
197        assert_eq!(list_data_type_string, expected_string);
198    }
199
200    #[test]
201    fn test_display_large_list() {
202        let large_list_data_type =
203            DataType::LargeList(Arc::new(Field::new_list_field(DataType::Int32, true)));
204        let large_list_data_type_string = large_list_data_type.to_string();
205        let expected_string = "LargeList(nullable Int32)";
206        assert_eq!(large_list_data_type_string, expected_string);
207
208        // Test with named field
209        let large_list_named =
210            DataType::LargeList(Arc::new(Field::new("bar", DataType::UInt64, false)));
211        let large_list_named_string = large_list_named.to_string();
212        let expected_named_string = "LargeList(UInt64, field: 'bar')";
213        assert_eq!(large_list_named_string, expected_named_string);
214
215        // Test with metadata
216        let mut field = Field::new_list_field(DataType::Int32, true);
217        let metadata = HashMap::from([("key1".to_string(), "value1".to_string())]);
218        field.set_metadata(metadata);
219        let large_list_metadata = DataType::LargeList(Arc::new(field));
220        let large_list_metadata_string = large_list_metadata.to_string();
221        let expected_metadata_string =
222            "LargeList(nullable Int32, metadata: {\"key1\": \"value1\"})";
223        assert_eq!(large_list_metadata_string, expected_metadata_string);
224    }
225
226    #[test]
227    fn test_display_fixed_size_list() {
228        let fixed_size_list =
229            DataType::FixedSizeList(Arc::new(Field::new_list_field(DataType::Int32, true)), 5);
230        let fixed_size_list_string = fixed_size_list.to_string();
231        let expected_string = "FixedSizeList(5 x nullable Int32)";
232        assert_eq!(fixed_size_list_string, expected_string);
233
234        // Test with named field
235        let fixed_size_named =
236            DataType::FixedSizeList(Arc::new(Field::new("baz", DataType::UInt64, false)), 3);
237        let fixed_size_named_string = fixed_size_named.to_string();
238        let expected_named_string = "FixedSizeList(3 x UInt64, field: 'baz')";
239        assert_eq!(fixed_size_named_string, expected_named_string);
240
241        // Test with metadata
242        let mut field = Field::new_list_field(DataType::Int32, true);
243        let metadata = HashMap::from([("key2".to_string(), "value2".to_string())]);
244        field.set_metadata(metadata);
245        let fixed_size_metadata = DataType::FixedSizeList(Arc::new(field), 4);
246        let fixed_size_metadata_string = fixed_size_metadata.to_string();
247        let expected_metadata_string =
248            "FixedSizeList(4 x nullable Int32, metadata: {\"key2\": \"value2\"})";
249        assert_eq!(fixed_size_metadata_string, expected_metadata_string);
250    }
251}