1use linkrs_core::{DataType, Value};
2
3#[derive(Debug, Clone)]
4pub struct ConversionError {
5 pub message: String,
6 pub step_index: Option<usize>,
7 pub source_type: Option<String>,
8 pub target_type: Option<String>,
9}
10
11impl ConversionError {
12 pub fn new(message: impl Into<String>) -> Self {
13 Self {
14 message: message.into(),
15 step_index: None,
16 source_type: None,
17 target_type: None,
18 }
19 }
20
21 pub fn with_step(mut self, idx: usize) -> Self {
22 self.step_index = Some(idx);
23 self
24 }
25
26 pub fn with_types(mut self, source: impl Into<String>, target: impl Into<String>) -> Self {
27 self.source_type = Some(source.into());
28 self.target_type = Some(target.into());
29 self
30 }
31}
32
33impl std::fmt::Display for ConversionError {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 write!(f, "Conversion error: {}", self.message)?;
36 if let Some(idx) = self.step_index {
37 write!(f, " [step {}]", idx + 1)?;
38 }
39 if let (Some(src), Some(tgt)) = (&self.source_type, &self.target_type) {
40 write!(f, " ({} -> {})", src, tgt)?;
41 } else if let Some(tgt) = &self.target_type {
42 write!(f, " (target {})", tgt)?;
43 }
44 Ok(())
45 }
46}
47
48impl std::error::Error for ConversionError {}
49
50macro_rules! conversion_err {
51 ($($arg:tt)*) => {
52 ConversionError::new(format!($($arg)*))
53 };
54}
55
56fn type_name(dt: &DataType) -> &'static str {
57 match dt {
58 DataType::Empty => "EMPTY",
59 DataType::Null => "NULL",
60 DataType::Bool => "BOOL",
61 DataType::SmallInt => "SMALLINT",
62 DataType::Int => "INT",
63 DataType::BigInt => "BIGINT",
64 DataType::Float => "FLOAT",
65 DataType::Double => "DOUBLE",
66 DataType::Decimal128 => "DECIMAL",
67 DataType::String => "STRING",
68 DataType::Date => "DATE",
69 DataType::Time => "TIME",
70 DataType::DateTime => "DATETIME",
71 DataType::Blob => "BLOB",
72 DataType::Uuid => "UUID",
73 DataType::Interval => "INTERVAL",
74 DataType::Json => "JSON",
75 DataType::JsonB => "JSONB",
76 DataType::FixedString(_) => "FIXED_STRING",
77 _ => "COMPLEX",
78 }
79}
80
81pub fn convert_value(value: &Value, target_type: &DataType) -> Result<Value, ConversionError> {
82 if value.is_null() || value.is_empty() {
83 return Ok(Value::Null(linkrs_core::value::null::NullType::Null));
84 }
85
86 let source_type = value.get_type();
87 if source_type == *target_type {
88 return Ok(value.clone());
89 }
90
91 match (value, target_type) {
92 (Value::SmallInt(i), DataType::Int) => Ok(Value::Int(*i as i32)),
93 (Value::SmallInt(i), DataType::BigInt) => Ok(Value::BigInt(*i as i64)),
94 (Value::SmallInt(i), DataType::Float) => Ok(Value::Float(*i as f32)),
95 (Value::SmallInt(i), DataType::Double) => Ok(Value::Double(*i as f64)),
96 (Value::SmallInt(i), DataType::String) => Ok(Value::string(i.to_string())),
97
98 (Value::Int(i), DataType::BigInt) => Ok(Value::BigInt(*i as i64)),
99 (Value::Int(i), DataType::Float) => Ok(Value::Float(*i as f32)),
100 (Value::Int(i), DataType::Double) => Ok(Value::Double(*i as f64)),
101 (Value::Int(i), DataType::SmallInt) => {
102 let v = *i;
103 if v < i16::MIN as i32 || v > i16::MAX as i32 {
104 return Err(conversion_err!("Value {} out of range for SMALLINT", v));
105 }
106 Ok(Value::SmallInt(v as i16))
107 }
108 (Value::Int(i), DataType::String) => Ok(Value::string(i.to_string())),
109
110 (Value::BigInt(i), DataType::Int) => {
111 let v = *i;
112 if v < i32::MIN as i64 || v > i32::MAX as i64 {
113 return Err(conversion_err!("Value {} out of range for INT", v));
114 }
115 Ok(Value::Int(v as i32))
116 }
117 (Value::BigInt(i), DataType::SmallInt) => {
118 let v = *i;
119 if v < i16::MIN as i64 || v > i16::MAX as i64 {
120 return Err(conversion_err!("Value {} out of range for SMALLINT", v));
121 }
122 Ok(Value::SmallInt(v as i16))
123 }
124 (Value::BigInt(i), DataType::Float) => Ok(Value::Float(*i as f32)),
125 (Value::BigInt(i), DataType::Double) => Ok(Value::Double(*i as f64)),
126 (Value::BigInt(i), DataType::String) => Ok(Value::string(i.to_string())),
127
128 (Value::Float(f), DataType::Double) => Ok(Value::Double(*f as f64)),
129 (Value::Float(f), DataType::String) => Ok(Value::string(f.to_string())),
130
131 (Value::Double(d), DataType::Float) => Ok(Value::Float(*d as f32)),
132 (Value::Double(d), DataType::String) => Ok(Value::string(d.to_string())),
133
134 (Value::String(s), DataType::SmallInt) => s
135 .parse::<i16>()
136 .map(Value::SmallInt)
137 .map_err(|e| conversion_err!("Cannot parse '{}' as SMALLINT: {}", s, e)),
138 (Value::String(s), DataType::Int) => s
139 .parse::<i32>()
140 .map(Value::Int)
141 .map_err(|e| conversion_err!("Cannot parse '{}' as INT: {}", s, e)),
142 (Value::String(s), DataType::BigInt) => s
143 .parse::<i64>()
144 .map(Value::BigInt)
145 .map_err(|e| conversion_err!("Cannot parse '{}' as BIGINT: {}", s, e)),
146 (Value::String(s), DataType::Float) => s
147 .parse::<f32>()
148 .map(Value::Float)
149 .map_err(|e| conversion_err!("Cannot parse '{}' as FLOAT: {}", s, e)),
150 (Value::String(s), DataType::Double) => s
151 .parse::<f64>()
152 .map(Value::Double)
153 .map_err(|e| conversion_err!("Cannot parse '{}' as DOUBLE: {}", s, e)),
154 (Value::String(s), DataType::Bool) => match s.to_lowercase().as_str() {
155 "true" | "yes" | "1" => Ok(Value::Bool(true)),
156 "false" | "no" | "0" => Ok(Value::Bool(false)),
157 _ => Err(conversion_err!("Cannot parse '{}' as BOOL", s)),
158 },
159
160 (_, DataType::String) => Ok(Value::string(format!("{}", value))),
161
162 _ => Err(conversion_err!(
163 "Unsupported conversion from {:?} to {}",
164 value.get_type(),
165 type_name(target_type),
166 )),
167 }
168}
169
170pub fn is_compatible_type(from: &DataType, to: &DataType) -> bool {
173 if from == to {
174 return true;
175 }
176 match (from, to) {
177 (
179 DataType::SmallInt,
180 DataType::Int
181 | DataType::BigInt
182 | DataType::Float
183 | DataType::Double
184 | DataType::String,
185 ) => true,
186 (
188 DataType::Int,
189 DataType::SmallInt
190 | DataType::BigInt
191 | DataType::Float
192 | DataType::Double
193 | DataType::String,
194 ) => true,
195 (
197 DataType::BigInt,
198 DataType::Int
199 | DataType::SmallInt
200 | DataType::Float
201 | DataType::Double
202 | DataType::String,
203 ) => true,
204 (DataType::Float, DataType::Double | DataType::String) => true,
206 (DataType::Double, DataType::Float | DataType::String) => true,
208 (
210 DataType::String,
211 DataType::SmallInt
212 | DataType::Int
213 | DataType::BigInt
214 | DataType::Float
215 | DataType::Double
216 | DataType::Bool,
217 ) => true,
218 (_, DataType::String) => true,
220 _ => false,
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227 use linkrs_core::value::null::NullType;
228
229 #[test]
230 fn test_identity_conversion() {
231 assert_eq!(
232 convert_value(&Value::Int(42), &DataType::Int).unwrap(),
233 Value::Int(42)
234 );
235 assert_eq!(
236 convert_value(&Value::string("hello"), &DataType::String).unwrap(),
237 Value::string("hello")
238 );
239 }
240
241 #[test]
242 fn test_int_to_bigint() {
243 assert_eq!(
244 convert_value(&Value::Int(42), &DataType::BigInt).unwrap(),
245 Value::BigInt(42)
246 );
247 }
248
249 #[test]
250 fn test_smallint_to_int() {
251 assert_eq!(
252 convert_value(&Value::SmallInt(100), &DataType::Int).unwrap(),
253 Value::Int(100)
254 );
255 }
256
257 #[test]
258 fn test_int_to_smallint_ok() {
259 assert_eq!(
260 convert_value(&Value::Int(42), &DataType::SmallInt).unwrap(),
261 Value::SmallInt(42)
262 );
263 }
264
265 #[test]
266 fn test_int_to_smallint_overflow() {
267 let result = convert_value(&Value::Int(99999), &DataType::SmallInt);
268 assert!(result.is_err());
269 }
270
271 #[test]
272 fn test_bigint_to_int_ok() {
273 assert_eq!(
274 convert_value(&Value::BigInt(42), &DataType::Int).unwrap(),
275 Value::Int(42)
276 );
277 }
278
279 #[test]
280 fn test_bigint_to_int_overflow() {
281 let result = convert_value(&Value::BigInt(9999999999i64), &DataType::Int);
282 assert!(result.is_err());
283 }
284
285 #[test]
286 fn test_float_to_double() {
287 let result = convert_value(&Value::Float(3.5), &DataType::Double).unwrap();
288 match result {
289 Value::Double(d) => assert!((d - 3.5_f64).abs() < 1e-6),
290 _ => panic!("Expected Double"),
291 }
292 }
293
294 #[test]
295 fn test_string_to_int() {
296 assert_eq!(
297 convert_value(&Value::string("42"), &DataType::Int).unwrap(),
298 Value::Int(42)
299 );
300 }
301
302 #[test]
303 fn test_string_to_bool() {
304 assert_eq!(
305 convert_value(&Value::string("true"), &DataType::Bool).unwrap(),
306 Value::Bool(true)
307 );
308 assert_eq!(
309 convert_value(&Value::string("false"), &DataType::Bool).unwrap(),
310 Value::Bool(false)
311 );
312 }
313
314 #[test]
315 fn test_string_to_bool_invalid() {
316 let result = convert_value(&Value::string("maybe"), &DataType::Bool);
317 assert!(result.is_err());
318 }
319
320 #[test]
321 fn test_null_conversion() {
322 let result = convert_value(&Value::Null(NullType::Null), &DataType::String).unwrap();
323 assert!(result.is_null());
324 }
325
326 #[test]
327 fn test_value_to_string() {
328 assert_eq!(
329 convert_value(&Value::Int(42), &DataType::String).unwrap(),
330 Value::string("42")
331 );
332 assert_eq!(
333 convert_value(&Value::Bool(true), &DataType::String).unwrap(),
334 Value::string("true")
335 );
336 }
337
338 #[test]
339 fn test_unsupported_conversion() {
340 let result = convert_value(&Value::Int(42), &DataType::Date);
341 assert!(result.is_err());
342 }
343}