1use serde::{Deserialize, Serialize};
7use std::fmt;
8
9#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
16pub enum LogicalType {
17 Any,
19
20 Null,
22
23 Bool,
25
26 Int8,
28
29 Int16,
31
32 Int32,
34
35 Int64,
37
38 Float32,
40
41 Float64,
43
44 String,
46
47 Bytes,
49
50 Date,
52
53 Time,
55
56 Timestamp,
58
59 Duration,
61
62 List(Box<LogicalType>),
64
65 Map {
67 key: Box<LogicalType>,
69 value: Box<LogicalType>,
71 },
72
73 Struct(Vec<(String, LogicalType)>),
75
76 Node,
78
79 Edge,
81
82 Path,
84}
85
86impl LogicalType {
87 #[must_use]
89 pub const fn is_numeric(&self) -> bool {
90 matches!(
91 self,
92 LogicalType::Int8
93 | LogicalType::Int16
94 | LogicalType::Int32
95 | LogicalType::Int64
96 | LogicalType::Float32
97 | LogicalType::Float64
98 )
99 }
100
101 #[must_use]
103 pub const fn is_integer(&self) -> bool {
104 matches!(
105 self,
106 LogicalType::Int8 | LogicalType::Int16 | LogicalType::Int32 | LogicalType::Int64
107 )
108 }
109
110 #[must_use]
112 pub const fn is_float(&self) -> bool {
113 matches!(self, LogicalType::Float32 | LogicalType::Float64)
114 }
115
116 #[must_use]
118 pub const fn is_temporal(&self) -> bool {
119 matches!(
120 self,
121 LogicalType::Date | LogicalType::Time | LogicalType::Timestamp | LogicalType::Duration
122 )
123 }
124
125 #[must_use]
127 pub const fn is_graph_element(&self) -> bool {
128 matches!(
129 self,
130 LogicalType::Node | LogicalType::Edge | LogicalType::Path
131 )
132 }
133
134 #[must_use]
138 pub const fn is_nullable(&self) -> bool {
139 true
140 }
141
142 #[must_use]
144 pub fn list_element_type(&self) -> Option<&LogicalType> {
145 match self {
146 LogicalType::List(elem) => Some(elem),
147 _ => None,
148 }
149 }
150
151 #[must_use]
153 pub fn can_coerce_from(&self, other: &LogicalType) -> bool {
154 if self == other {
155 return true;
156 }
157
158 if matches!(self, LogicalType::Any) {
160 return true;
161 }
162
163 if matches!(other, LogicalType::Null) && self.is_nullable() {
165 return true;
166 }
167
168 match (other, self) {
170 (LogicalType::Int8, LogicalType::Int16 | LogicalType::Int32 | LogicalType::Int64) => {
171 true
172 }
173 (LogicalType::Int16, LogicalType::Int32 | LogicalType::Int64) => true,
174 (LogicalType::Int32, LogicalType::Int64) => true,
175 (LogicalType::Float32, LogicalType::Float64) => true,
176 (
178 LogicalType::Int8 | LogicalType::Int16 | LogicalType::Int32,
179 LogicalType::Float32 | LogicalType::Float64,
180 ) => true,
181 (LogicalType::Int64, LogicalType::Float64) => true,
182 _ => false,
183 }
184 }
185
186 #[must_use]
188 pub fn common_type(&self, other: &LogicalType) -> Option<LogicalType> {
189 if self == other {
190 return Some(self.clone());
191 }
192
193 if matches!(self, LogicalType::Any) {
195 return Some(other.clone());
196 }
197 if matches!(other, LogicalType::Any) {
198 return Some(self.clone());
199 }
200
201 if matches!(self, LogicalType::Null) {
203 return Some(other.clone());
204 }
205 if matches!(other, LogicalType::Null) {
206 return Some(self.clone());
207 }
208
209 if self.is_numeric() && other.is_numeric() {
211 if self.is_float() || other.is_float() {
213 return Some(LogicalType::Float64);
214 }
215 return Some(LogicalType::Int64);
217 }
218
219 None
220 }
221}
222
223impl fmt::Display for LogicalType {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 match self {
226 LogicalType::Any => write!(f, "ANY"),
227 LogicalType::Null => write!(f, "NULL"),
228 LogicalType::Bool => write!(f, "BOOL"),
229 LogicalType::Int8 => write!(f, "INT8"),
230 LogicalType::Int16 => write!(f, "INT16"),
231 LogicalType::Int32 => write!(f, "INT32"),
232 LogicalType::Int64 => write!(f, "INT64"),
233 LogicalType::Float32 => write!(f, "FLOAT32"),
234 LogicalType::Float64 => write!(f, "FLOAT64"),
235 LogicalType::String => write!(f, "STRING"),
236 LogicalType::Bytes => write!(f, "BYTES"),
237 LogicalType::Date => write!(f, "DATE"),
238 LogicalType::Time => write!(f, "TIME"),
239 LogicalType::Timestamp => write!(f, "TIMESTAMP"),
240 LogicalType::Duration => write!(f, "DURATION"),
241 LogicalType::List(elem) => write!(f, "LIST<{elem}>"),
242 LogicalType::Map { key, value } => write!(f, "MAP<{key}, {value}>"),
243 LogicalType::Struct(fields) => {
244 write!(f, "STRUCT<")?;
245 for (i, (name, ty)) in fields.iter().enumerate() {
246 if i > 0 {
247 write!(f, ", ")?;
248 }
249 write!(f, "{name}: {ty}")?;
250 }
251 write!(f, ">")
252 }
253 LogicalType::Node => write!(f, "NODE"),
254 LogicalType::Edge => write!(f, "EDGE"),
255 LogicalType::Path => write!(f, "PATH"),
256 }
257 }
258}
259
260impl Default for LogicalType {
261 fn default() -> Self {
262 LogicalType::Any
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[test]
271 fn test_numeric_checks() {
272 assert!(LogicalType::Int64.is_numeric());
273 assert!(LogicalType::Float64.is_numeric());
274 assert!(!LogicalType::String.is_numeric());
275
276 assert!(LogicalType::Int64.is_integer());
277 assert!(!LogicalType::Float64.is_integer());
278
279 assert!(LogicalType::Float64.is_float());
280 assert!(!LogicalType::Int64.is_float());
281 }
282
283 #[test]
284 fn test_coercion() {
285 assert!(LogicalType::Int64.can_coerce_from(&LogicalType::Int64));
287
288 assert!(LogicalType::Int64.can_coerce_from(&LogicalType::Null));
290 assert!(LogicalType::String.can_coerce_from(&LogicalType::Null));
291
292 assert!(LogicalType::Int64.can_coerce_from(&LogicalType::Int32));
294 assert!(LogicalType::Int32.can_coerce_from(&LogicalType::Int16));
295 assert!(!LogicalType::Int32.can_coerce_from(&LogicalType::Int64));
296
297 assert!(LogicalType::Float64.can_coerce_from(&LogicalType::Float32));
299
300 assert!(LogicalType::Float64.can_coerce_from(&LogicalType::Int64));
302 assert!(LogicalType::Float32.can_coerce_from(&LogicalType::Int32));
303 }
304
305 #[test]
306 fn test_common_type() {
307 assert_eq!(
309 LogicalType::Int64.common_type(&LogicalType::Int64),
310 Some(LogicalType::Int64)
311 );
312
313 assert_eq!(
315 LogicalType::Int32.common_type(&LogicalType::Int64),
316 Some(LogicalType::Int64)
317 );
318 assert_eq!(
319 LogicalType::Int64.common_type(&LogicalType::Float64),
320 Some(LogicalType::Float64)
321 );
322
323 assert_eq!(
325 LogicalType::Null.common_type(&LogicalType::String),
326 Some(LogicalType::String)
327 );
328
329 assert_eq!(LogicalType::String.common_type(&LogicalType::Int64), None);
331 }
332
333 #[test]
334 fn test_display() {
335 assert_eq!(LogicalType::Int64.to_string(), "INT64");
336 assert_eq!(
337 LogicalType::List(Box::new(LogicalType::String)).to_string(),
338 "LIST<STRING>"
339 );
340 assert_eq!(
341 LogicalType::Map {
342 key: Box::new(LogicalType::String),
343 value: Box::new(LogicalType::Int64)
344 }
345 .to_string(),
346 "MAP<STRING, INT64>"
347 );
348 }
349}