sql_cli/sql/functions/
convert.rs1use 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
7pub 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')", "SELECT CONVERT(0, 'celsius', 'fahrenheit')", "SELECT CONVERT(1, 'gallon', 'liters')", "SELECT CONVERT(70, 'kg', 'pounds')", "SELECT CONVERT(1, 'bar', 'psi')", ],
25 }
26 }
27
28 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
29 self.validate_args(args)?;
30
31 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 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 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 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 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 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 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 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}