sql_cli/sql/functions/
convert.rs

1use anyhow::{anyhow, Result};
2
3use crate::data::datatable::DataValue;
4use crate::data::unit_converter::convert_units;
5use crate::sql::functions::{ArgCount, FunctionCategory, FunctionSignature, SqlFunction};
6
7/// CONVERT function - Convert values between different units
8pub struct ConvertFunction;
9
10impl SqlFunction for ConvertFunction {
11    fn signature(&self) -> FunctionSignature {
12        FunctionSignature {
13            name: "CONVERT",
14            category: FunctionCategory::Conversion,
15            arg_count: ArgCount::Fixed(3),
16            description: "Convert a value from one unit to another",
17            returns: "FLOAT",
18            examples: vec![
19                "SELECT CONVERT(100, 'km', 'miles')",         // Distance
20                "SELECT CONVERT(0, 'celsius', 'fahrenheit')", // Temperature
21                "SELECT CONVERT(1, 'gallon', 'liters')",      // Volume
22                "SELECT CONVERT(70, 'kg', 'pounds')",         // Weight
23                "SELECT CONVERT(1, 'bar', 'psi')",            // Pressure
24            ],
25        }
26    }
27
28    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
29        self.validate_args(args)?;
30
31        // Extract numeric value
32        let value = match &args[0] {
33            DataValue::Integer(n) => *n as f64,
34            DataValue::Float(f) => *f,
35            DataValue::Null => return Ok(DataValue::Null),
36            _ => return Err(anyhow!("CONVERT first argument must be numeric")),
37        };
38
39        // Extract from_unit
40        let from_unit = match &args[1] {
41            DataValue::String(s) => s.as_str(),
42            DataValue::InternedString(s) => s.as_str(),
43            DataValue::Null => return Ok(DataValue::Null),
44            _ => {
45                return Err(anyhow!(
46                    "CONVERT second argument must be a string (from_unit)"
47                ))
48            }
49        };
50
51        // Extract to_unit
52        let to_unit = match &args[2] {
53            DataValue::String(s) => s.as_str(),
54            DataValue::InternedString(s) => s.as_str(),
55            DataValue::Null => return Ok(DataValue::Null),
56            _ => return Err(anyhow!("CONVERT third argument must be a string (to_unit)")),
57        };
58
59        // Perform conversion
60        match convert_units(value, from_unit, to_unit) {
61            Ok(result) => Ok(DataValue::Float(result)),
62            Err(e) => Err(anyhow!("Unit conversion failed: {}", e)),
63        }
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn test_convert_distance() {
73        let func = ConvertFunction;
74
75        // Test km to miles
76        let args = vec![
77            DataValue::Float(100.0),
78            DataValue::String("km".to_string()),
79            DataValue::String("miles".to_string()),
80        ];
81        let result = func.evaluate(&args).unwrap();
82        if let DataValue::Float(val) = result {
83            assert!((val - 62.137).abs() < 0.01);
84        } else {
85            panic!("Expected Float result");
86        }
87    }
88
89    #[test]
90    fn test_convert_temperature() {
91        let func = ConvertFunction;
92
93        // Test Celsius to Fahrenheit (0°C = 32°F)
94        let args = vec![
95            DataValue::Integer(0),
96            DataValue::String("celsius".to_string()),
97            DataValue::String("fahrenheit".to_string()),
98        ];
99        let result = func.evaluate(&args).unwrap();
100        if let DataValue::Float(val) = result {
101            assert!((val - 32.0).abs() < 0.01);
102        } else {
103            panic!("Expected Float result");
104        }
105    }
106
107    #[test]
108    fn test_convert_with_null() {
109        let func = ConvertFunction;
110
111        // Test with NULL value
112        let args = vec![
113            DataValue::Null,
114            DataValue::String("km".to_string()),
115            DataValue::String("miles".to_string()),
116        ];
117        let result = func.evaluate(&args).unwrap();
118        assert!(matches!(result, DataValue::Null));
119    }
120
121    #[test]
122    fn test_convert_invalid_unit() {
123        let func = ConvertFunction;
124
125        // Test with invalid unit
126        let args = vec![
127            DataValue::Float(100.0),
128            DataValue::String("invalid_unit".to_string()),
129            DataValue::String("miles".to_string()),
130        ];
131        let result = func.evaluate(&args);
132        assert!(result.is_err());
133    }
134}