ddb_core/functions/
conversion.rs

1// Type conversion functions
2use super::Value;
3use crate::error::{DdbError, Result};
4
5/// CAST(value AS type) - SQL standard type conversion
6pub fn cast(value: &Value, target_type: &str) -> Result<Value> {
7    match target_type.to_uppercase().as_str() {
8        "INTEGER" | "INT" | "BIGINT" => {
9            Ok(Value::Integer(value.as_i64()?))
10        }
11        "FLOAT" | "REAL" | "DOUBLE" | "DECIMAL" | "NUMERIC" => {
12            Ok(Value::Float(value.as_f64()?))
13        }
14        "VARCHAR" | "CHAR" | "TEXT" | "STRING" => {
15            Ok(Value::String(value.to_string()))
16        }
17        "BOOLEAN" | "BOOL" => {
18            let val = match value {
19                Value::Boolean(b) => *b,
20                Value::Integer(i) => *i != 0,
21                Value::Float(f) => *f != 0.0,
22                Value::String(s) => {
23                    s.eq_ignore_ascii_case("true")
24                    || s.eq_ignore_ascii_case("yes")
25                    || s.eq_ignore_ascii_case("1")
26                }
27                Value::Null => false,
28                _ => return Err(DdbError::TypeError(format!("Cannot convert {:?} to boolean", value))),
29            };
30            Ok(Value::Boolean(val))
31        }
32        "DATE" => {
33            let s = value.to_string();
34            let date = chrono::NaiveDate::parse_from_str(&s, "%Y-%m-%d")
35                .map_err(|e| DdbError::TypeError(format!("Invalid date format: {}", e)))?;
36            Ok(Value::Date(date))
37        }
38        "DATETIME" | "TIMESTAMP" => {
39            let s = value.to_string();
40            let dt = chrono::NaiveDateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S")
41                .or_else(|_| chrono::NaiveDateTime::parse_from_str(&s, "%Y-%m-%d"))
42                .map_err(|e| DdbError::TypeError(format!("Invalid datetime format: {}", e)))?;
43            Ok(Value::DateTime(dt))
44        }
45        _ => Err(DdbError::TypeError(format!("Unknown type: {}", target_type))),
46    }
47}
48
49/// CONVERT(value, type) - MySQL-style conversion
50pub fn convert(value: &Value, target_type: &str) -> Result<Value> {
51    cast(value, target_type)
52}
53
54/// ATOF(str) - ASCII to float
55pub fn atof(value: &Value) -> Result<Value> {
56    let s = value.to_string();
57    s.parse::<f64>()
58        .map(Value::Float)
59        .map_err(|_| DdbError::TypeError(format!("Cannot parse '{}' as float", s)))
60}
61
62/// ATOI(str) - ASCII to integer
63pub fn atoi(value: &Value) -> Result<Value> {
64    let s = value.to_string();
65    s.parse::<i64>()
66        .map(Value::Integer)
67        .map_err(|_| DdbError::TypeError(format!("Cannot parse '{}' as integer", s)))
68}
69
70/// TO_STRING(value) / STR(value) - Convert to string
71pub fn to_string(value: &Value) -> Result<Value> {
72    Ok(Value::String(value.to_string()))
73}
74
75/// TO_NUMBER(str) - Convert string to number (int or float)
76pub fn to_number(value: &Value) -> Result<Value> {
77    let s = value.to_string();
78
79    // Try integer first
80    if let Ok(i) = s.parse::<i64>() {
81        return Ok(Value::Integer(i));
82    }
83
84    // Try float
85    if let Ok(f) = s.parse::<f64>() {
86        return Ok(Value::Float(f));
87    }
88
89    Err(DdbError::TypeError(format!("Cannot convert '{}' to number", s)))
90}
91
92/// HEX(n) - Convert to hexadecimal string
93pub fn hex(value: &Value) -> Result<Value> {
94    match value {
95        Value::Integer(i) => Ok(Value::String(format!("{:X}", i))),
96        _ => {
97            let i = value.as_i64()?;
98            Ok(Value::String(format!("{:X}", i)))
99        }
100    }
101}
102
103/// BIN(n) - Convert to binary string
104pub fn bin(value: &Value) -> Result<Value> {
105    match value {
106        Value::Integer(i) => Ok(Value::String(format!("{:b}", i))),
107        _ => {
108            let i = value.as_i64()?;
109            Ok(Value::String(format!("{:b}", i)))
110        }
111    }
112}
113
114/// OCT(n) - Convert to octal string
115pub fn oct(value: &Value) -> Result<Value> {
116    match value {
117        Value::Integer(i) => Ok(Value::String(format!("{:o}", i))),
118        _ => {
119            let i = value.as_i64()?;
120            Ok(Value::String(format!("{:o}", i)))
121        }
122    }
123}
124
125/// FORMAT(number, decimals) - Format number with thousands separator
126pub fn format(value: &Value, decimals: &Value) -> Result<Value> {
127    let num = value.as_f64()?;
128    let dec = decimals.as_i64()? as usize;
129
130    let formatted = format!("{:.width$}", num, width = dec);
131
132    // Add thousands separators
133    let parts: Vec<&str> = formatted.split('.').collect();
134    let integer_part = parts[0];
135    let decimal_part = parts.get(1).unwrap_or(&"");
136
137    let mut result = String::new();
138    for (i, ch) in integer_part.chars().rev().enumerate() {
139        if i > 0 && i % 3 == 0 {
140            result.insert(0, ',');
141        }
142        result.insert(0, ch);
143    }
144
145    if !decimal_part.is_empty() {
146        result.push('.');
147        result.push_str(decimal_part);
148    }
149
150    Ok(Value::String(result))
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_cast() {
159        assert_eq!(
160            cast(&Value::from("123"), "INTEGER").unwrap(),
161            Value::Integer(123)
162        );
163        assert_eq!(
164            cast(&Value::Integer(42), "STRING").unwrap(),
165            Value::from("42")
166        );
167    }
168
169    #[test]
170    fn test_atof_atoi() {
171        assert_eq!(atof(&Value::from("3.14")).unwrap(), Value::Float(3.14));
172        assert_eq!(atoi(&Value::from("42")).unwrap(), Value::Integer(42));
173    }
174
175    #[test]
176    fn test_hex_bin_oct() {
177        assert_eq!(hex(&Value::Integer(255)).unwrap(), Value::from("FF"));
178        assert_eq!(bin(&Value::Integer(7)).unwrap(), Value::from("111"));
179        assert_eq!(oct(&Value::Integer(8)).unwrap(), Value::from("10"));
180    }
181
182    #[test]
183    fn test_to_number() {
184        assert_eq!(to_number(&Value::from("123")).unwrap(), Value::Integer(123));
185        assert_eq!(to_number(&Value::from("12.5")).unwrap(), Value::Float(12.5));
186    }
187}