use vibesql_types::SqlValue;
pub fn coerce_to_string(value: &SqlValue) -> Option<String> {
match value {
SqlValue::Null => None,
SqlValue::Varchar(s) | SqlValue::Character(s) => Some(s.to_string()),
SqlValue::Integer(n) | SqlValue::Bigint(n) => Some(n.to_string()),
SqlValue::Smallint(n) => Some(n.to_string()),
SqlValue::Unsigned(n) => Some(n.to_string()),
SqlValue::Double(n) | SqlValue::Numeric(n) | SqlValue::Real(n) => {
Some(format_sqlite_float(*n))
}
SqlValue::Float(n) => Some(format_sqlite_float(*n as f64)),
SqlValue::Boolean(b) => Some(if *b { "1" } else { "0" }.to_string()),
SqlValue::Vector(v) => {
let inner: Vec<String> = v.iter().map(|f| f.to_string()).collect();
Some(format!("[{}]", inner.join(",")))
}
SqlValue::Date(d) => Some(d.to_string()),
SqlValue::Time(t) => Some(t.to_string()),
SqlValue::Timestamp(ts) => Some(ts.to_string()),
SqlValue::Interval(i) => Some(i.to_string()),
SqlValue::Blob(bytes) => Some(String::from_utf8_lossy(bytes).to_string()),
}
}
pub fn coerce_to_number(value: &SqlValue) -> Option<f64> {
match value {
SqlValue::Null => None,
SqlValue::Integer(n) | SqlValue::Bigint(n) => Some(*n as f64),
SqlValue::Smallint(n) => Some(*n as f64),
SqlValue::Unsigned(n) => Some(*n as f64),
SqlValue::Double(n) | SqlValue::Numeric(n) | SqlValue::Real(n) => Some(*n),
SqlValue::Float(n) => Some(*n as f64),
SqlValue::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
SqlValue::Varchar(s) | SqlValue::Character(s) => {
Some(parse_sqlite_number(s))
}
_ => Some(0.0),
}
}
pub fn coerce_to_integer(value: &SqlValue) -> Option<i64> {
match value {
SqlValue::Null => None,
SqlValue::Integer(n) | SqlValue::Bigint(n) => Some(*n),
SqlValue::Smallint(n) => Some(*n as i64),
SqlValue::Unsigned(n) => Some(*n as i64),
SqlValue::Double(n) | SqlValue::Numeric(n) | SqlValue::Real(n) => Some(*n as i64),
SqlValue::Float(n) => Some(*n as i64),
SqlValue::Boolean(b) => Some(if *b { 1 } else { 0 }),
SqlValue::Varchar(s) | SqlValue::Character(s) => {
Some(s.trim().parse::<i64>().unwrap_or_else(|_| {
s.trim().parse::<f64>().map(|f| f as i64).unwrap_or(0)
}))
}
_ => Some(0),
}
}
fn format_sqlite_float(n: f64) -> String {
if n.is_nan() {
return "NaN".to_string();
}
if n.is_infinite() {
return if n.is_sign_positive() { "Inf" } else { "-Inf" }.to_string();
}
if n.fract() == 0.0 && n.abs() < 1e15 {
format!("{:.1}", n)
} else {
n.to_string()
}
}
fn parse_sqlite_number(s: &str) -> f64 {
let s = s.trim();
if s.is_empty() {
return 0.0;
}
if let Ok(n) = s.parse::<f64>() {
return n;
}
let mut end = 0;
let mut has_dot = false;
let mut has_e = false;
let chars: Vec<char> = s.chars().collect();
if !chars.is_empty() && (chars[0] == '-' || chars[0] == '+') {
end = 1;
}
while end < chars.len() {
let c = chars[end];
if c.is_ascii_digit() {
end += 1;
} else if c == '.' && !has_dot && !has_e {
has_dot = true;
end += 1;
} else if (c == 'e' || c == 'E') && !has_e && end > 0 {
has_e = true;
end += 1;
if end < chars.len() && (chars[end] == '-' || chars[end] == '+') {
end += 1;
}
} else {
break;
}
}
if end == 0 || (end == 1 && (chars[0] == '-' || chars[0] == '+')) {
return 0.0;
}
let numeric_part: String = chars[..end].iter().collect();
numeric_part.parse::<f64>().unwrap_or(0.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_coerce_to_string() {
assert_eq!(coerce_to_string(&SqlValue::Null), None);
assert_eq!(coerce_to_string(&SqlValue::Integer(123)), Some("123".to_string()));
assert_eq!(
coerce_to_string(&SqlValue::Varchar(arcstr::ArcStr::from("hello"))),
Some("hello".to_string())
);
}
#[test]
fn test_coerce_to_number() {
assert_eq!(coerce_to_number(&SqlValue::Null), None);
assert_eq!(coerce_to_number(&SqlValue::Integer(123)), Some(123.0));
assert_eq!(coerce_to_number(&SqlValue::Varchar(arcstr::ArcStr::from("456"))), Some(456.0));
assert_eq!(coerce_to_number(&SqlValue::Varchar(arcstr::ArcStr::from("free"))), Some(0.0));
assert_eq!(coerce_to_number(&SqlValue::Varchar(arcstr::ArcStr::from("-5"))), Some(-5.0));
}
#[test]
fn test_parse_sqlite_number() {
assert_eq!(parse_sqlite_number("123"), 123.0);
assert_eq!(parse_sqlite_number(" 456 "), 456.0);
assert_eq!(parse_sqlite_number("-5"), -5.0);
assert_eq!(parse_sqlite_number("free"), 0.0);
assert_eq!(parse_sqlite_number("123abc"), 123.0);
assert_eq!(parse_sqlite_number(""), 0.0);
}
}