Skip to main content

akar_function/scalar/
cast.rs

1use crate::registry::*;
2use akar_common::types::{Date, Interval, Timestamp, Value};
3use time::{Date as TimeDate, Month, Time as TimeTime};
4
5// ==================== Cast ====================
6
7pub(crate) fn evaluate_cast(target: CastTarget, args: &[Value]) -> Result<Value, String> {
8    if args.is_empty() {
9        return Err("Cast requires an argument".into());
10    }
11    let v = &args[0];
12
13    match target {
14        CastTarget::String => Ok(Value::String(format!("{:?}", v))),
15        CastTarget::Int64 => match v {
16            Value::Int64(x) => Ok(Value::Int64(*x)),
17            Value::Int32(x) => Ok(Value::Int64(*x as i64)),
18            Value::Int16(x) => Ok(Value::Int64(*x as i64)),
19            Value::Int8(x) => Ok(Value::Int64(*x as i64)),
20            Value::UInt64(x) => Ok(Value::Int64(*x as i64)),
21            Value::UInt32(x) => Ok(Value::Int64(*x as i64)),
22            Value::UInt16(x) => Ok(Value::Int64(*x as i64)),
23            Value::UInt8(x) => Ok(Value::Int64(*x as i64)),
24            Value::Double(x) => Ok(Value::Int64(*x as i64)),
25            Value::Float(x) => Ok(Value::Int64(*x as i64)),
26            Value::Bool(x) => Ok(Value::Int64(if *x { 1 } else { 0 })),
27            Value::String(s) => s
28                .parse::<i64>()
29                .map(Value::Int64)
30                .map_err(|e| format!("Cannot cast string to int: {e}")),
31            _ => Err("Cannot cast to Int64".into()),
32        },
33        CastTarget::Int32 => match v {
34            Value::Int32(x) => Ok(Value::Int32(*x)),
35            Value::Int64(x) => Ok(Value::Int32(*x as i32)),
36            Value::Int16(x) => Ok(Value::Int32(*x as i32)),
37            Value::Int8(x) => Ok(Value::Int32(*x as i32)),
38            Value::Double(x) => Ok(Value::Int32(*x as i32)),
39            Value::Float(x) => Ok(Value::Int32(*x as i32)),
40            Value::String(s) => s
41                .parse::<i32>()
42                .map(Value::Int32)
43                .map_err(|e| format!("Cannot cast string to int32: {e}")),
44            _ => Err("Cannot cast to Int32".into()),
45        },
46        CastTarget::Double => match v {
47            Value::Int64(x) => Ok(Value::Double(*x as f64)),
48            Value::Int32(x) => Ok(Value::Double(*x as f64)),
49            Value::Int16(x) => Ok(Value::Double(*x as f64)),
50            Value::Int8(x) => Ok(Value::Double(*x as f64)),
51            Value::Double(x) => Ok(Value::Double(*x)),
52            Value::Float(x) => Ok(Value::Double(*x as f64)),
53            Value::String(s) => s
54                .parse::<f64>()
55                .map(Value::Double)
56                .map_err(|e| format!("Cannot cast string to double: {e}")),
57            _ => Err("Cannot cast to Double".into()),
58        },
59        CastTarget::Float => match v {
60            Value::Float(x) => Ok(Value::Float(*x)),
61            Value::Int64(x) => Ok(Value::Float(*x as f32)),
62            Value::Int32(x) => Ok(Value::Float(*x as f32)),
63            Value::Double(x) => Ok(Value::Float(*x as f32)),
64            Value::String(s) => s
65                .parse::<f32>()
66                .map(Value::Float)
67                .map_err(|e| format!("Cannot cast string to float: {e}")),
68            _ => Err("Cannot cast to Float".into()),
69        },
70        CastTarget::Bool => match v {
71            Value::Bool(x) => Ok(Value::Bool(*x)),
72            Value::Int64(x) => Ok(Value::Bool(*x != 0)),
73            Value::Int32(x) => Ok(Value::Bool(*x != 0)),
74            Value::String(s) => {
75                let lower = s.to_lowercase();
76                match lower.as_str() {
77                    "true" | "yes" | "1" => Ok(Value::Bool(true)),
78                    "false" | "no" | "0" => Ok(Value::Bool(false)),
79                    _ => Err(format!("Cannot cast string '{}' to Bool", s)),
80                }
81            }
82            _ => Err("Cannot cast to Bool".into()),
83        },
84        CastTarget::Date => match v {
85            Value::Date(x) => Ok(Value::Date(*x)),
86            Value::Timestamp(t) => {
87                // Convert timestamp to date by extracting days from micros
88                let days = (t.0.div_euclid(1_000_000) / 86400) as i32;
89                Ok(Value::Date(Date(days)))
90            }
91            Value::String(s) => parse_date_string(s).map(Value::Date),
92            _ => Err("Cannot cast to Date".into()),
93        },
94        CastTarget::Timestamp => match v {
95            Value::Timestamp(x) => Ok(Value::Timestamp(*x)),
96            Value::Date(d) => Ok(Value::Timestamp(Timestamp(d.0 as i64 * 86400 * 1_000_000))),
97            Value::String(s) => parse_timestamp_string(s).map(Value::Timestamp),
98            _ => Err("Cannot cast to Timestamp".into()),
99        },
100        CastTarget::Interval => match v {
101            Value::Interval(x) => Ok(Value::Interval(*x)),
102            Value::Int64(x) => {
103                // Treat as microseconds
104                Ok(Value::Interval(Interval {
105                    months: 0,
106                    days: 0,
107                    micros: *x,
108                }))
109            }
110            _ => Err("Cannot cast to Interval".into()),
111        },
112    }
113}
114
115/// Parse `YYYY-MM-DD` (or `YYYY-M-D`) into days since epoch.
116fn parse_date_string(s: &str) -> Result<Date, String> {
117    let s = s.trim();
118    let parts: Vec<&str> = s.split('-').collect();
119    if parts.len() != 3 {
120        return Err(format!("Cannot parse date '{}': expected YYYY-MM-DD", s));
121    }
122    let year: i32 = parts[0].parse().map_err(|_| format!("Invalid year in '{}'", s))?;
123    let month: u32 = parts[1].parse().map_err(|_| format!("Invalid month in '{}'", s))?;
124    let day: u8 = parts[2].parse().map_err(|_| format!("Invalid day in '{}'", s))?;
125    let month_enum = Month::try_from(month as u8).map_err(|_| format!("Invalid month in '{}'", s))?;
126    let date = TimeDate::from_calendar_date(year, month_enum, day).map_err(|e| format!("Invalid date '{}': {e}", s))?;
127    let epoch = TimeDate::from_calendar_date(1970, Month::January, 1).map_err(|e| format!("Date error: {e}"))?;
128    let days = (date - epoch).whole_days() as i32;
129    Ok(Date(days))
130}
131
132/// Parse `YYYY-MM-DD[ HH:MM:SS[.fraction]]` into microseconds since epoch.
133fn parse_timestamp_string(s: &str) -> Result<Timestamp, String> {
134    let s = s.trim();
135    let (date_part, time_part) = match s.find(' ') {
136        Some(idx) => (&s[..idx], &s[idx + 1..]),
137        None => (s, "00:00:00"),
138    };
139    let date = parse_date_string(date_part)?;
140    let mut time_parts: Vec<&str> = time_part.split(':').collect();
141    if time_parts.len() != 3 {
142        return Err(format!("Cannot parse time '{}': expected HH:MM:SS", time_part));
143    }
144    let sec_str = time_parts.pop().unwrap_or("0");
145    let (sec_str, frac_micros) = match sec_str.find('.') {
146        Some(idx) => {
147            let frac = &sec_str[idx + 1..];
148            let micros = if frac.is_empty() {
149                0
150            } else {
151                let padded = format!("{:<6}", frac);
152                padded.chars().take(6).collect::<String>().parse::<i64>().unwrap_or(0)
153            };
154            (&sec_str[..idx], micros)
155        }
156        None => (sec_str, 0),
157    };
158    let hour: u8 = time_parts[0]
159        .parse()
160        .map_err(|_| format!("Invalid hour in '{}'", time_part))?;
161    let minute: u8 = time_parts[1]
162        .parse()
163        .map_err(|_| format!("Invalid minute in '{}'", time_part))?;
164    let second: u8 = sec_str
165        .parse()
166        .map_err(|_| format!("Invalid second in '{}'", time_part))?;
167    let time = TimeTime::from_hms(hour, minute, second).map_err(|e| format!("Invalid time '{}': {e}", time_part))?;
168    let seconds = time.as_hms().0 as i64 * 3600 + time.as_hms().1 as i64 * 60 + time.as_hms().2 as i64;
169    let micros = (date.0 as i64 * 86400) + seconds * 1_000_000 + frac_micros;
170    Ok(Timestamp(micros))
171}