alopex_sql/planner/types.rs
1//! Type definitions for the Alopex SQL planner.
2//!
3//! This module defines [`ResolvedType`], the normalized type representation
4//! used during type checking and planning phases.
5
6use crate::ast::ddl::{DataType, VectorMetric};
7
8/// Normalized type information for type checking.
9///
10/// This enum represents the resolved type after normalization from AST types.
11/// For example, `INTEGER` and `INT` both resolve to [`ResolvedType::Integer`].
12///
13/// # Examples
14///
15/// ```
16/// use alopex_sql::planner::types::ResolvedType;
17/// use alopex_sql::ast::ddl::{DataType, VectorMetric};
18///
19/// // Convert from AST DataType
20/// let int_type = ResolvedType::from_ast(&DataType::Integer);
21/// assert_eq!(int_type, ResolvedType::Integer);
22///
23/// // VECTOR with omitted metric defaults to Cosine
24/// let vec_type = ResolvedType::from_ast(&DataType::Vector { dimension: 128, metric: None });
25/// assert_eq!(vec_type, ResolvedType::Vector { dimension: 128, metric: VectorMetric::Cosine });
26/// ```
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum ResolvedType {
29 /// Integer type (INTEGER, INT → Integer)
30 Integer,
31 /// Big integer type
32 BigInt,
33 /// Single-precision floating point
34 Float,
35 /// Double-precision floating point
36 Double,
37 /// Text/string type
38 Text,
39 /// Binary data type
40 Blob,
41 /// Boolean type (BOOLEAN, BOOL → Boolean)
42 Boolean,
43 /// Timestamp type
44 Timestamp,
45 /// Vector type with dimension and metric
46 /// Metric is always populated (defaults to Cosine if omitted in AST)
47 Vector {
48 dimension: u32,
49 metric: VectorMetric,
50 },
51 /// NULL type (for NULL literals)
52 Null,
53}
54
55impl ResolvedType {
56 /// Convert from AST [`DataType`] to [`ResolvedType`].
57 ///
58 /// For `VECTOR` types, if metric is omitted in the AST, it defaults to `Cosine`.
59 ///
60 /// # Examples
61 ///
62 /// ```
63 /// use alopex_sql::planner::types::ResolvedType;
64 /// use alopex_sql::ast::ddl::{DataType, VectorMetric};
65 ///
66 /// // INTEGER and INT both resolve to Integer
67 /// assert_eq!(ResolvedType::from_ast(&DataType::Integer), ResolvedType::Integer);
68 /// assert_eq!(ResolvedType::from_ast(&DataType::Int), ResolvedType::Integer);
69 ///
70 /// // BOOLEAN and BOOL both resolve to Boolean
71 /// assert_eq!(ResolvedType::from_ast(&DataType::Boolean), ResolvedType::Boolean);
72 /// assert_eq!(ResolvedType::from_ast(&DataType::Bool), ResolvedType::Boolean);
73 ///
74 /// // VECTOR with metric
75 /// let vec_with_metric = ResolvedType::from_ast(&DataType::Vector {
76 /// dimension: 128,
77 /// metric: Some(VectorMetric::L2),
78 /// });
79 /// assert_eq!(vec_with_metric, ResolvedType::Vector {
80 /// dimension: 128,
81 /// metric: VectorMetric::L2,
82 /// });
83 ///
84 /// // VECTOR without metric defaults to Cosine
85 /// let vec_default = ResolvedType::from_ast(&DataType::Vector {
86 /// dimension: 256,
87 /// metric: None,
88 /// });
89 /// assert_eq!(vec_default, ResolvedType::Vector {
90 /// dimension: 256,
91 /// metric: VectorMetric::Cosine,
92 /// });
93 /// ```
94 pub fn from_ast(dt: &DataType) -> Self {
95 match dt {
96 DataType::Integer | DataType::Int => Self::Integer,
97 DataType::BigInt => Self::BigInt,
98 DataType::Float => Self::Float,
99 DataType::Double => Self::Double,
100 DataType::Text => Self::Text,
101 DataType::Blob => Self::Blob,
102 DataType::Boolean | DataType::Bool => Self::Boolean,
103 DataType::Timestamp => Self::Timestamp,
104 DataType::Vector { dimension, metric } => Self::Vector {
105 dimension: *dimension,
106 metric: metric.unwrap_or(VectorMetric::Cosine),
107 },
108 }
109 }
110
111 /// Check if this type can be implicitly cast to the target type.
112 ///
113 /// Implicit conversion rules:
114 /// - Same types are always compatible
115 /// - `Null` can be cast to any type
116 /// - Numeric widening: `Integer` → `BigInt`, `Float`, `Double`
117 /// - Numeric widening: `BigInt` → `Double`
118 /// - Numeric widening: `Float` → `Double`
119 /// - `Vector` types require dimension check (done separately)
120 ///
121 /// # Examples
122 ///
123 /// ```
124 /// use alopex_sql::planner::types::ResolvedType;
125 ///
126 /// // Same type
127 /// assert!(ResolvedType::Integer.can_cast_to(&ResolvedType::Integer));
128 ///
129 /// // Null can cast to any type
130 /// assert!(ResolvedType::Null.can_cast_to(&ResolvedType::Integer));
131 /// assert!(ResolvedType::Null.can_cast_to(&ResolvedType::Text));
132 ///
133 /// // Numeric widening
134 /// assert!(ResolvedType::Integer.can_cast_to(&ResolvedType::BigInt));
135 /// assert!(ResolvedType::Integer.can_cast_to(&ResolvedType::Float));
136 /// assert!(ResolvedType::Integer.can_cast_to(&ResolvedType::Double));
137 /// assert!(ResolvedType::BigInt.can_cast_to(&ResolvedType::Double));
138 /// assert!(ResolvedType::Float.can_cast_to(&ResolvedType::Double));
139 ///
140 /// // Incompatible types
141 /// assert!(!ResolvedType::Text.can_cast_to(&ResolvedType::Integer));
142 /// assert!(!ResolvedType::BigInt.can_cast_to(&ResolvedType::Integer));
143 /// ```
144 pub fn can_cast_to(&self, target: &ResolvedType) -> bool {
145 use ResolvedType::*;
146
147 match (self, target) {
148 // Same type is always compatible
149 (a, b) if a == b => true,
150
151 // Null can be cast to any type
152 (Null, _) => true,
153
154 // Numeric widening conversions
155 (Integer, BigInt | Float | Double) => true,
156 (BigInt, Double) => true,
157 (Float, Double) => true,
158
159 // Vector types require dimension check (done separately in TypeChecker)
160 (Vector { .. }, Vector { .. }) => false,
161
162 // All other conversions are not allowed
163 _ => false,
164 }
165 }
166
167 /// Get a human-readable name for this type.
168 ///
169 /// Used for error messages.
170 pub fn type_name(&self) -> &'static str {
171 match self {
172 Self::Integer => "Integer",
173 Self::BigInt => "BigInt",
174 Self::Float => "Float",
175 Self::Double => "Double",
176 Self::Text => "Text",
177 Self::Blob => "Blob",
178 Self::Boolean => "Boolean",
179 Self::Timestamp => "Timestamp",
180 Self::Vector { .. } => "Vector",
181 Self::Null => "Null",
182 }
183 }
184}
185
186impl std::fmt::Display for ResolvedType {
187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188 match self {
189 Self::Integer => write!(f, "INTEGER"),
190 Self::BigInt => write!(f, "BIGINT"),
191 Self::Float => write!(f, "FLOAT"),
192 Self::Double => write!(f, "DOUBLE"),
193 Self::Text => write!(f, "TEXT"),
194 Self::Blob => write!(f, "BLOB"),
195 Self::Boolean => write!(f, "BOOLEAN"),
196 Self::Timestamp => write!(f, "TIMESTAMP"),
197 Self::Vector { dimension, metric } => {
198 write!(f, "VECTOR({}, {:?})", dimension, metric)
199 }
200 Self::Null => write!(f, "NULL"),
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn test_from_ast_integer() {
211 assert_eq!(
212 ResolvedType::from_ast(&DataType::Integer),
213 ResolvedType::Integer
214 );
215 assert_eq!(
216 ResolvedType::from_ast(&DataType::Int),
217 ResolvedType::Integer
218 );
219 }
220
221 #[test]
222 fn test_from_ast_boolean() {
223 assert_eq!(
224 ResolvedType::from_ast(&DataType::Boolean),
225 ResolvedType::Boolean
226 );
227 assert_eq!(
228 ResolvedType::from_ast(&DataType::Bool),
229 ResolvedType::Boolean
230 );
231 }
232
233 #[test]
234 fn test_from_ast_vector_with_metric() {
235 let dt = DataType::Vector {
236 dimension: 128,
237 metric: Some(VectorMetric::L2),
238 };
239 assert_eq!(
240 ResolvedType::from_ast(&dt),
241 ResolvedType::Vector {
242 dimension: 128,
243 metric: VectorMetric::L2,
244 }
245 );
246 }
247
248 #[test]
249 fn test_from_ast_vector_default_metric() {
250 let dt = DataType::Vector {
251 dimension: 256,
252 metric: None,
253 };
254 assert_eq!(
255 ResolvedType::from_ast(&dt),
256 ResolvedType::Vector {
257 dimension: 256,
258 metric: VectorMetric::Cosine,
259 }
260 );
261 }
262
263 #[test]
264 fn test_can_cast_same_type() {
265 assert!(ResolvedType::Integer.can_cast_to(&ResolvedType::Integer));
266 assert!(ResolvedType::Text.can_cast_to(&ResolvedType::Text));
267 }
268
269 #[test]
270 fn test_can_cast_null() {
271 assert!(ResolvedType::Null.can_cast_to(&ResolvedType::Integer));
272 assert!(ResolvedType::Null.can_cast_to(&ResolvedType::Text));
273 assert!(ResolvedType::Null.can_cast_to(&ResolvedType::Boolean));
274 }
275
276 #[test]
277 fn test_can_cast_numeric_widening() {
278 // Integer → BigInt/Float/Double
279 assert!(ResolvedType::Integer.can_cast_to(&ResolvedType::BigInt));
280 assert!(ResolvedType::Integer.can_cast_to(&ResolvedType::Float));
281 assert!(ResolvedType::Integer.can_cast_to(&ResolvedType::Double));
282
283 // BigInt → Double
284 assert!(ResolvedType::BigInt.can_cast_to(&ResolvedType::Double));
285
286 // Float → Double
287 assert!(ResolvedType::Float.can_cast_to(&ResolvedType::Double));
288 }
289
290 #[test]
291 fn test_can_cast_incompatible() {
292 // Text cannot cast to numeric
293 assert!(!ResolvedType::Text.can_cast_to(&ResolvedType::Integer));
294
295 // Numeric narrowing not allowed
296 assert!(!ResolvedType::BigInt.can_cast_to(&ResolvedType::Integer));
297 assert!(!ResolvedType::Double.can_cast_to(&ResolvedType::Float));
298 }
299
300 #[test]
301 fn test_can_cast_vector() {
302 let vec1 = ResolvedType::Vector {
303 dimension: 128,
304 metric: VectorMetric::Cosine,
305 };
306 let vec2 = ResolvedType::Vector {
307 dimension: 128,
308 metric: VectorMetric::L2,
309 };
310 // Vector dimension check is done separately
311 assert!(!vec1.can_cast_to(&vec2));
312 }
313
314 #[test]
315 fn test_display() {
316 assert_eq!(format!("{}", ResolvedType::Integer), "INTEGER");
317 assert_eq!(format!("{}", ResolvedType::Text), "TEXT");
318 assert_eq!(
319 format!(
320 "{}",
321 ResolvedType::Vector {
322 dimension: 128,
323 metric: VectorMetric::Cosine
324 }
325 ),
326 "VECTOR(128, Cosine)"
327 );
328 }
329}