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 Vector(usize),
88}
89
90impl LogicalType {
91 #[must_use]
93 pub const fn is_numeric(&self) -> bool {
94 matches!(
95 self,
96 LogicalType::Int8
97 | LogicalType::Int16
98 | LogicalType::Int32
99 | LogicalType::Int64
100 | LogicalType::Float32
101 | LogicalType::Float64
102 )
103 }
104
105 #[must_use]
107 pub const fn is_integer(&self) -> bool {
108 matches!(
109 self,
110 LogicalType::Int8 | LogicalType::Int16 | LogicalType::Int32 | LogicalType::Int64
111 )
112 }
113
114 #[must_use]
116 pub const fn is_float(&self) -> bool {
117 matches!(self, LogicalType::Float32 | LogicalType::Float64)
118 }
119
120 #[must_use]
122 pub const fn is_temporal(&self) -> bool {
123 matches!(
124 self,
125 LogicalType::Date | LogicalType::Time | LogicalType::Timestamp | LogicalType::Duration
126 )
127 }
128
129 #[must_use]
131 pub const fn is_graph_element(&self) -> bool {
132 matches!(
133 self,
134 LogicalType::Node | LogicalType::Edge | LogicalType::Path
135 )
136 }
137
138 #[must_use]
142 pub const fn is_nullable(&self) -> bool {
143 true
144 }
145
146 #[must_use]
148 pub fn list_element_type(&self) -> Option<&LogicalType> {
149 match self {
150 LogicalType::List(elem) => Some(elem),
151 _ => None,
152 }
153 }
154
155 #[must_use]
157 pub const fn is_vector(&self) -> bool {
158 matches!(self, LogicalType::Vector(_))
159 }
160
161 #[must_use]
163 pub const fn vector_dimensions(&self) -> Option<usize> {
164 match self {
165 LogicalType::Vector(dim) => Some(*dim),
166 _ => None,
167 }
168 }
169
170 #[must_use]
172 pub fn can_coerce_from(&self, other: &LogicalType) -> bool {
173 if self == other {
174 return true;
175 }
176
177 if matches!(self, LogicalType::Any) {
179 return true;
180 }
181
182 if matches!(other, LogicalType::Null) && self.is_nullable() {
184 return true;
185 }
186
187 match (other, self) {
189 (LogicalType::Int8, LogicalType::Int16 | LogicalType::Int32 | LogicalType::Int64) => {
190 true
191 }
192 (LogicalType::Int16, LogicalType::Int32 | LogicalType::Int64) => true,
193 (LogicalType::Int32, LogicalType::Int64) => true,
194 (LogicalType::Float32, LogicalType::Float64) => true,
195 (
197 LogicalType::Int8 | LogicalType::Int16 | LogicalType::Int32,
198 LogicalType::Float32 | LogicalType::Float64,
199 ) => true,
200 (LogicalType::Int64, LogicalType::Float64) => true,
201 _ => false,
202 }
203 }
204
205 #[must_use]
207 pub fn common_type(&self, other: &LogicalType) -> Option<LogicalType> {
208 if self == other {
209 return Some(self.clone());
210 }
211
212 if matches!(self, LogicalType::Any) {
214 return Some(other.clone());
215 }
216 if matches!(other, LogicalType::Any) {
217 return Some(self.clone());
218 }
219
220 if matches!(self, LogicalType::Null) {
222 return Some(other.clone());
223 }
224 if matches!(other, LogicalType::Null) {
225 return Some(self.clone());
226 }
227
228 if self.is_numeric() && other.is_numeric() {
230 if self.is_float() || other.is_float() {
232 return Some(LogicalType::Float64);
233 }
234 return Some(LogicalType::Int64);
236 }
237
238 None
239 }
240}
241
242impl fmt::Display for LogicalType {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 match self {
245 LogicalType::Any => write!(f, "ANY"),
246 LogicalType::Null => write!(f, "NULL"),
247 LogicalType::Bool => write!(f, "BOOL"),
248 LogicalType::Int8 => write!(f, "INT8"),
249 LogicalType::Int16 => write!(f, "INT16"),
250 LogicalType::Int32 => write!(f, "INT32"),
251 LogicalType::Int64 => write!(f, "INT64"),
252 LogicalType::Float32 => write!(f, "FLOAT32"),
253 LogicalType::Float64 => write!(f, "FLOAT64"),
254 LogicalType::String => write!(f, "STRING"),
255 LogicalType::Bytes => write!(f, "BYTES"),
256 LogicalType::Date => write!(f, "DATE"),
257 LogicalType::Time => write!(f, "TIME"),
258 LogicalType::Timestamp => write!(f, "TIMESTAMP"),
259 LogicalType::Duration => write!(f, "DURATION"),
260 LogicalType::List(elem) => write!(f, "LIST<{elem}>"),
261 LogicalType::Map { key, value } => write!(f, "MAP<{key}, {value}>"),
262 LogicalType::Struct(fields) => {
263 write!(f, "STRUCT<")?;
264 for (i, (name, ty)) in fields.iter().enumerate() {
265 if i > 0 {
266 write!(f, ", ")?;
267 }
268 write!(f, "{name}: {ty}")?;
269 }
270 write!(f, ">")
271 }
272 LogicalType::Node => write!(f, "NODE"),
273 LogicalType::Edge => write!(f, "EDGE"),
274 LogicalType::Path => write!(f, "PATH"),
275 LogicalType::Vector(dim) => write!(f, "VECTOR({dim})"),
276 }
277 }
278}
279
280impl Default for LogicalType {
281 fn default() -> Self {
282 LogicalType::Any
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn test_numeric_checks() {
292 assert!(LogicalType::Int64.is_numeric());
293 assert!(LogicalType::Float64.is_numeric());
294 assert!(!LogicalType::String.is_numeric());
295
296 assert!(LogicalType::Int64.is_integer());
297 assert!(!LogicalType::Float64.is_integer());
298
299 assert!(LogicalType::Float64.is_float());
300 assert!(!LogicalType::Int64.is_float());
301 }
302
303 #[test]
304 fn test_coercion() {
305 assert!(LogicalType::Int64.can_coerce_from(&LogicalType::Int64));
307
308 assert!(LogicalType::Int64.can_coerce_from(&LogicalType::Null));
310 assert!(LogicalType::String.can_coerce_from(&LogicalType::Null));
311
312 assert!(LogicalType::Int64.can_coerce_from(&LogicalType::Int32));
314 assert!(LogicalType::Int32.can_coerce_from(&LogicalType::Int16));
315 assert!(!LogicalType::Int32.can_coerce_from(&LogicalType::Int64));
316
317 assert!(LogicalType::Float64.can_coerce_from(&LogicalType::Float32));
319
320 assert!(LogicalType::Float64.can_coerce_from(&LogicalType::Int64));
322 assert!(LogicalType::Float32.can_coerce_from(&LogicalType::Int32));
323 }
324
325 #[test]
326 fn test_common_type() {
327 assert_eq!(
329 LogicalType::Int64.common_type(&LogicalType::Int64),
330 Some(LogicalType::Int64)
331 );
332
333 assert_eq!(
335 LogicalType::Int32.common_type(&LogicalType::Int64),
336 Some(LogicalType::Int64)
337 );
338 assert_eq!(
339 LogicalType::Int64.common_type(&LogicalType::Float64),
340 Some(LogicalType::Float64)
341 );
342
343 assert_eq!(
345 LogicalType::Null.common_type(&LogicalType::String),
346 Some(LogicalType::String)
347 );
348
349 assert_eq!(LogicalType::String.common_type(&LogicalType::Int64), None);
351 }
352
353 #[test]
354 fn test_display() {
355 assert_eq!(LogicalType::Int64.to_string(), "INT64");
356 assert_eq!(
357 LogicalType::List(Box::new(LogicalType::String)).to_string(),
358 "LIST<STRING>"
359 );
360 assert_eq!(
361 LogicalType::Map {
362 key: Box::new(LogicalType::String),
363 value: Box::new(LogicalType::Int64)
364 }
365 .to_string(),
366 "MAP<STRING, INT64>"
367 );
368 }
369
370 #[test]
371 fn test_vector_type() {
372 let v384 = LogicalType::Vector(384);
373 let v768 = LogicalType::Vector(768);
374 let v1536 = LogicalType::Vector(1536);
375
376 assert!(v384.is_vector());
378 assert!(v768.is_vector());
379 assert!(!LogicalType::Float64.is_vector());
380 assert!(!LogicalType::List(Box::new(LogicalType::Float32)).is_vector());
381
382 assert_eq!(v384.vector_dimensions(), Some(384));
384 assert_eq!(v768.vector_dimensions(), Some(768));
385 assert_eq!(v1536.vector_dimensions(), Some(1536));
386 assert_eq!(LogicalType::Float64.vector_dimensions(), None);
387
388 assert_eq!(v384.to_string(), "VECTOR(384)");
390 assert_eq!(v768.to_string(), "VECTOR(768)");
391 assert_eq!(v1536.to_string(), "VECTOR(1536)");
392
393 assert_eq!(LogicalType::Vector(384), LogicalType::Vector(384));
395 assert_ne!(LogicalType::Vector(384), LogicalType::Vector(768));
396
397 assert!(!v384.is_numeric());
399 assert!(!v384.is_integer());
400 assert!(!v384.is_float());
401 }
402}