Skip to main content

datafusion_common/types/
logical.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 super::NativeType;
19use crate::error::Result;
20use arrow::datatypes::DataType;
21use core::fmt;
22use std::{cmp::Ordering, hash::Hash, sync::Arc};
23
24/// Signature that uniquely identifies a type among other types.
25#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
26pub enum TypeSignature<'a> {
27    /// Represents a built-in native type.
28    Native(&'a NativeType),
29    /// Represents an arrow-compatible extension type.
30    /// (<https://arrow.apache.org/docs/format/Columnar.html#extension-types>)
31    ///
32    /// The `name` should contain the same value as 'ARROW:extension:name'.
33    Extension {
34        name: &'a str,
35        parameters: &'a [TypeParameter<'a>],
36    },
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
40pub enum TypeParameter<'a> {
41    Type(TypeSignature<'a>),
42    Number(i128),
43}
44
45/// A reference counted [`LogicalType`].
46pub type LogicalTypeRef = Arc<dyn LogicalType>;
47
48/// Representation of a logical type with its signature and its native backing
49/// type.
50///
51/// The logical type is meant to be used during the DataFusion logical planning
52/// phase in order to reason about logical types without worrying about their
53/// underlying physical implementation.
54///
55/// ### Extension types
56///
57/// [`LogicalType`] is a trait in order to allow the possibility of declaring
58/// extension types:
59///
60/// ```
61/// use datafusion_common::types::{LogicalType, NativeType, TypeSignature};
62///
63/// struct JSON {}
64///
65/// impl LogicalType for JSON {
66///     fn native(&self) -> &NativeType {
67///         &NativeType::String
68///     }
69///
70///     fn signature(&self) -> TypeSignature<'_> {
71///         TypeSignature::Extension {
72///             name: "JSON",
73///             parameters: &[],
74///         }
75///     }
76/// }
77/// ```
78pub trait LogicalType: Sync + Send {
79    /// Get the native backing type of this logical type.
80    fn native(&self) -> &NativeType;
81    /// Get the unique type signature for this logical type. Logical types with identical
82    /// signatures are considered equal.
83    fn signature(&self) -> TypeSignature<'_>;
84
85    /// Get the default physical type to cast `origin` to in order to obtain a physical type
86    /// that is logically compatible with this logical type.
87    fn default_cast_for(&self, origin: &DataType) -> Result<DataType> {
88        self.native().default_cast_for(origin)
89    }
90}
91
92impl fmt::Debug for dyn LogicalType {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        f.debug_tuple("LogicalType")
95            .field(&self.signature())
96            .field(&self.native())
97            .finish()
98    }
99}
100
101impl std::fmt::Display for dyn LogicalType {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        match self.signature() {
104            TypeSignature::Native(_) => write!(f, "{}", self.native()),
105            TypeSignature::Extension { name, .. } => write!(f, "{name}"),
106        }
107    }
108}
109
110impl PartialEq for dyn LogicalType {
111    fn eq(&self, other: &Self) -> bool {
112        // Logical types with identical signatures are considered equal.
113        self.signature().eq(&other.signature())
114    }
115}
116
117impl Eq for dyn LogicalType {}
118
119impl PartialOrd for dyn LogicalType {
120    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
121        Some(self.cmp(other))
122    }
123}
124
125impl Ord for dyn LogicalType {
126    fn cmp(&self, other: &Self) -> Ordering {
127        // Logical types with identical signatures are considered equal.
128        self.signature().cmp(&other.signature())
129    }
130}
131
132impl Hash for dyn LogicalType {
133    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
134        // Logical types with identical signatures are considered equal.
135        self.signature().hash(state);
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    use crate::types::{
143        LogicalField, LogicalFields, logical_boolean, logical_date, logical_float32,
144        logical_float64, logical_int32, logical_int64, logical_null, logical_string,
145    };
146    use arrow::datatypes::{Field, Fields};
147    use insta::assert_snapshot;
148
149    #[test]
150    fn test_logical_type_display_simple() {
151        assert_snapshot!(logical_null(), @"Null");
152        assert_snapshot!(logical_boolean(), @"Boolean");
153        assert_snapshot!(logical_int32(), @"Int32");
154        assert_snapshot!(logical_int64(), @"Int64");
155        assert_snapshot!(logical_float32(), @"Float32");
156        assert_snapshot!(logical_float64(), @"Float64");
157        assert_snapshot!(logical_string(), @"String");
158        assert_snapshot!(logical_date(), @"Date");
159    }
160
161    #[test]
162    fn test_logical_type_display_list() {
163        let list_type: Arc<dyn LogicalType> = Arc::new(NativeType::List(Arc::new(
164            LogicalField::from(&Field::new("item", DataType::Int32, true)),
165        )));
166        assert_snapshot!(list_type, @"List(Int32)");
167    }
168
169    #[test]
170    fn test_logical_type_display_struct() {
171        let struct_type: Arc<dyn LogicalType> = Arc::new(NativeType::Struct(
172            LogicalFields::from(&Fields::from(vec![
173                Field::new("x", DataType::Float64, false),
174                Field::new("y", DataType::Float64, true),
175            ])),
176        ));
177        assert_snapshot!(struct_type, @r#"Struct("x": non-null Float64, "y": Float64)"#);
178    }
179
180    #[test]
181    fn test_logical_type_display_fixed_size_list() {
182        let fsl_type: Arc<dyn LogicalType> = Arc::new(NativeType::FixedSizeList(
183            Arc::new(LogicalField::from(&Field::new(
184                "item",
185                DataType::Float32,
186                false,
187            ))),
188            3,
189        ));
190        assert_snapshot!(fsl_type, @"FixedSizeList(3 x non-null Float32)");
191    }
192
193    #[test]
194    fn test_logical_type_display_map() {
195        let map_type: Arc<dyn LogicalType> = Arc::new(NativeType::Map(Arc::new(
196            LogicalField::from(&Field::new("entries", DataType::Utf8, false)),
197        )));
198        assert_snapshot!(map_type, @"Map(non-null String)");
199    }
200
201    #[test]
202    fn test_logical_type_display_union() {
203        use arrow::datatypes::UnionFields;
204
205        let union_fields = UnionFields::try_new(
206            vec![0, 1],
207            vec![
208                Field::new("int_val", DataType::Int32, false),
209                Field::new("str_val", DataType::Utf8, true),
210            ],
211        )
212        .unwrap();
213        let union_type: Arc<dyn LogicalType> = Arc::new(NativeType::Union(
214            crate::types::LogicalUnionFields::from(&union_fields),
215        ));
216        assert_snapshot!(union_type, @r#"Union(0: ("int_val": non-null Int32), 1: ("str_val": String))"#);
217    }
218
219    #[test]
220    fn test_logical_type_display_nullable_vs_non_nullable() {
221        let nullable_list: Arc<dyn LogicalType> = Arc::new(NativeType::List(Arc::new(
222            LogicalField::from(&Field::new("item", DataType::Int32, true)),
223        )));
224        let non_nullable_list: Arc<dyn LogicalType> =
225            Arc::new(NativeType::List(Arc::new(LogicalField::from(&Field::new(
226                "item",
227                DataType::Int32,
228                false,
229            )))));
230
231        assert_snapshot!(nullable_list, @"List(Int32)");
232        assert_snapshot!(non_nullable_list, @"List(non-null Int32)");
233    }
234
235    #[test]
236    fn test_logical_type_display_extension() {
237        struct JsonType;
238        impl LogicalType for JsonType {
239            fn native(&self) -> &NativeType {
240                &NativeType::String
241            }
242            fn signature(&self) -> TypeSignature<'_> {
243                TypeSignature::Extension {
244                    name: "JSON",
245                    parameters: &[],
246                }
247            }
248        }
249        let json: Arc<dyn LogicalType> = Arc::new(JsonType);
250        assert_snapshot!(json, @"JSON");
251    }
252}