1use serde::{Deserialize, Serialize};
7use std::fmt;
8
9#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub enum LogicalType {
15 Any,
17
18 Null,
20
21 Bool,
23
24 Int8,
26
27 Int16,
29
30 Int32,
32
33 Int64,
35
36 Float32,
38
39 Float64,
41
42 String,
44
45 Bytes,
47
48 Date,
50
51 Time,
53
54 Timestamp,
56
57 Duration,
59
60 List(Box<LogicalType>),
62
63 Map {
65 key: Box<LogicalType>,
67 value: Box<LogicalType>,
69 },
70
71 Struct(Vec<(String, LogicalType)>),
73
74 Node,
76
77 Edge,
79
80 Path,
82}
83
84impl LogicalType {
85 #[must_use]
87 pub const fn is_numeric(&self) -> bool {
88 matches!(
89 self,
90 LogicalType::Int8
91 | LogicalType::Int16
92 | LogicalType::Int32
93 | LogicalType::Int64
94 | LogicalType::Float32
95 | LogicalType::Float64
96 )
97 }
98
99 #[must_use]
101 pub const fn is_integer(&self) -> bool {
102 matches!(
103 self,
104 LogicalType::Int8 | LogicalType::Int16 | LogicalType::Int32 | LogicalType::Int64
105 )
106 }
107
108 #[must_use]
110 pub const fn is_float(&self) -> bool {
111 matches!(self, LogicalType::Float32 | LogicalType::Float64)
112 }
113
114 #[must_use]
116 pub const fn is_temporal(&self) -> bool {
117 matches!(
118 self,
119 LogicalType::Date | LogicalType::Time | LogicalType::Timestamp | LogicalType::Duration
120 )
121 }
122
123 #[must_use]
125 pub const fn is_graph_element(&self) -> bool {
126 matches!(
127 self,
128 LogicalType::Node | LogicalType::Edge | LogicalType::Path
129 )
130 }
131
132 #[must_use]
136 pub const fn is_nullable(&self) -> bool {
137 true
138 }
139
140 #[must_use]
142 pub fn list_element_type(&self) -> Option<&LogicalType> {
143 match self {
144 LogicalType::List(elem) => Some(elem),
145 _ => None,
146 }
147 }
148
149 #[must_use]
151 pub fn can_coerce_from(&self, other: &LogicalType) -> bool {
152 if self == other {
153 return true;
154 }
155
156 if matches!(self, LogicalType::Any) {
158 return true;
159 }
160
161 if matches!(other, LogicalType::Null) && self.is_nullable() {
163 return true;
164 }
165
166 match (other, self) {
168 (LogicalType::Int8, LogicalType::Int16 | LogicalType::Int32 | LogicalType::Int64) => {
169 true
170 }
171 (LogicalType::Int16, LogicalType::Int32 | LogicalType::Int64) => true,
172 (LogicalType::Int32, LogicalType::Int64) => true,
173 (LogicalType::Float32, LogicalType::Float64) => true,
174 (
176 LogicalType::Int8 | LogicalType::Int16 | LogicalType::Int32,
177 LogicalType::Float32 | LogicalType::Float64,
178 ) => true,
179 (LogicalType::Int64, LogicalType::Float64) => true,
180 _ => false,
181 }
182 }
183
184 #[must_use]
186 pub fn common_type(&self, other: &LogicalType) -> Option<LogicalType> {
187 if self == other {
188 return Some(self.clone());
189 }
190
191 if matches!(self, LogicalType::Any) {
193 return Some(other.clone());
194 }
195 if matches!(other, LogicalType::Any) {
196 return Some(self.clone());
197 }
198
199 if matches!(self, LogicalType::Null) {
201 return Some(other.clone());
202 }
203 if matches!(other, LogicalType::Null) {
204 return Some(self.clone());
205 }
206
207 if self.is_numeric() && other.is_numeric() {
209 if self.is_float() || other.is_float() {
211 return Some(LogicalType::Float64);
212 }
213 return Some(LogicalType::Int64);
215 }
216
217 None
218 }
219}
220
221impl fmt::Display for LogicalType {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 match self {
224 LogicalType::Any => write!(f, "ANY"),
225 LogicalType::Null => write!(f, "NULL"),
226 LogicalType::Bool => write!(f, "BOOL"),
227 LogicalType::Int8 => write!(f, "INT8"),
228 LogicalType::Int16 => write!(f, "INT16"),
229 LogicalType::Int32 => write!(f, "INT32"),
230 LogicalType::Int64 => write!(f, "INT64"),
231 LogicalType::Float32 => write!(f, "FLOAT32"),
232 LogicalType::Float64 => write!(f, "FLOAT64"),
233 LogicalType::String => write!(f, "STRING"),
234 LogicalType::Bytes => write!(f, "BYTES"),
235 LogicalType::Date => write!(f, "DATE"),
236 LogicalType::Time => write!(f, "TIME"),
237 LogicalType::Timestamp => write!(f, "TIMESTAMP"),
238 LogicalType::Duration => write!(f, "DURATION"),
239 LogicalType::List(elem) => write!(f, "LIST<{elem}>"),
240 LogicalType::Map { key, value } => write!(f, "MAP<{key}, {value}>"),
241 LogicalType::Struct(fields) => {
242 write!(f, "STRUCT<")?;
243 for (i, (name, ty)) in fields.iter().enumerate() {
244 if i > 0 {
245 write!(f, ", ")?;
246 }
247 write!(f, "{name}: {ty}")?;
248 }
249 write!(f, ">")
250 }
251 LogicalType::Node => write!(f, "NODE"),
252 LogicalType::Edge => write!(f, "EDGE"),
253 LogicalType::Path => write!(f, "PATH"),
254 }
255 }
256}
257
258impl Default for LogicalType {
259 fn default() -> Self {
260 LogicalType::Any
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn test_numeric_checks() {
270 assert!(LogicalType::Int64.is_numeric());
271 assert!(LogicalType::Float64.is_numeric());
272 assert!(!LogicalType::String.is_numeric());
273
274 assert!(LogicalType::Int64.is_integer());
275 assert!(!LogicalType::Float64.is_integer());
276
277 assert!(LogicalType::Float64.is_float());
278 assert!(!LogicalType::Int64.is_float());
279 }
280
281 #[test]
282 fn test_coercion() {
283 assert!(LogicalType::Int64.can_coerce_from(&LogicalType::Int64));
285
286 assert!(LogicalType::Int64.can_coerce_from(&LogicalType::Null));
288 assert!(LogicalType::String.can_coerce_from(&LogicalType::Null));
289
290 assert!(LogicalType::Int64.can_coerce_from(&LogicalType::Int32));
292 assert!(LogicalType::Int32.can_coerce_from(&LogicalType::Int16));
293 assert!(!LogicalType::Int32.can_coerce_from(&LogicalType::Int64));
294
295 assert!(LogicalType::Float64.can_coerce_from(&LogicalType::Float32));
297
298 assert!(LogicalType::Float64.can_coerce_from(&LogicalType::Int64));
300 assert!(LogicalType::Float32.can_coerce_from(&LogicalType::Int32));
301 }
302
303 #[test]
304 fn test_common_type() {
305 assert_eq!(
307 LogicalType::Int64.common_type(&LogicalType::Int64),
308 Some(LogicalType::Int64)
309 );
310
311 assert_eq!(
313 LogicalType::Int32.common_type(&LogicalType::Int64),
314 Some(LogicalType::Int64)
315 );
316 assert_eq!(
317 LogicalType::Int64.common_type(&LogicalType::Float64),
318 Some(LogicalType::Float64)
319 );
320
321 assert_eq!(
323 LogicalType::Null.common_type(&LogicalType::String),
324 Some(LogicalType::String)
325 );
326
327 assert_eq!(LogicalType::String.common_type(&LogicalType::Int64), None);
329 }
330
331 #[test]
332 fn test_display() {
333 assert_eq!(LogicalType::Int64.to_string(), "INT64");
334 assert_eq!(
335 LogicalType::List(Box::new(LogicalType::String)).to_string(),
336 "LIST<STRING>"
337 );
338 assert_eq!(
339 LogicalType::Map {
340 key: Box::new(LogicalType::String),
341 value: Box::new(LogicalType::Int64)
342 }
343 .to_string(),
344 "MAP<STRING, INT64>"
345 );
346 }
347}