Skip to main content

linkrs_migration/
converter.rs

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
170/// Check if a type conversion is supported (without needing an actual value).
171/// Mirrors the conversion paths in [`convert_value`].
172pub fn is_compatible_type(from: &DataType, to: &DataType) -> bool {
173    if from == to {
174        return true;
175    }
176    match (from, to) {
177        // SmallInt widening
178        (
179            DataType::SmallInt,
180            DataType::Int
181            | DataType::BigInt
182            | DataType::Float
183            | DataType::Double
184            | DataType::String,
185        ) => true,
186        // Int widening/narrowing
187        (
188            DataType::Int,
189            DataType::SmallInt
190            | DataType::BigInt
191            | DataType::Float
192            | DataType::Double
193            | DataType::String,
194        ) => true,
195        // BigInt widening/narrowing
196        (
197            DataType::BigInt,
198            DataType::Int
199            | DataType::SmallInt
200            | DataType::Float
201            | DataType::Double
202            | DataType::String,
203        ) => true,
204        // Float widening
205        (DataType::Float, DataType::Double | DataType::String) => true,
206        // Double narrowing
207        (DataType::Double, DataType::Float | DataType::String) => true,
208        // String parsing
209        (
210            DataType::String,
211            DataType::SmallInt
212            | DataType::Int
213            | DataType::BigInt
214            | DataType::Float
215            | DataType::Double
216            | DataType::Bool,
217        ) => true,
218        // Any type to String
219        (_, 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}