use vibesql_types::SqlValue;
use crate::errors::ExecutorError;
pub(crate) struct StringOps;
impl StringOps {
#[inline]
fn to_concat_string(value: &SqlValue) -> String {
use SqlValue::*;
match value {
Varchar(s) | Character(s) => s.to_string(),
Integer(i) => i.to_string(),
Smallint(i) => i.to_string(),
Bigint(i) => i.to_string(),
Unsigned(u) => u.to_string(),
Boolean(b) => if *b { "1" } else { "0" }.to_string(),
Numeric(n) => format_float(*n),
Float(f) => format_float(*f as f64),
Real(r) => format_float(*r as f64),
Double(d) => format_float(*d),
Date(d) => d.to_string(),
Time(t) => t.to_string(),
Timestamp(ts) => ts.to_string(),
Interval(i) => i.to_string(),
Vector(v) => {
let formatted: Vec<String> = v.iter().map(|x| x.to_string()).collect();
format!("[{}]", formatted.join(", "))
}
Blob(b) => {
let hex: String = b.iter().map(|byte| format!("{:02X}", byte)).collect();
hex
}
Null => "NULL".to_string(),
}
}
#[inline]
pub fn concat(left: &SqlValue, right: &SqlValue) -> Result<SqlValue, ExecutorError> {
let left_str = Self::to_concat_string(left);
let right_str = Self::to_concat_string(right);
Ok(SqlValue::Varchar(arcstr::ArcStr::from(format!("{}{}", left_str, right_str))))
}
}
#[inline]
fn format_float(n: f64) -> String {
if n.is_nan() {
"NaN".to_string()
} else if n.is_infinite() {
if n > 0.0 {
"Infinity".to_string()
} else {
"-Infinity".to_string()
}
} else if n.fract() == 0.0 && n.abs() < 1e15 {
format!("{}.0", n as i64)
} else {
n.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_varchar_concat() {
let result = StringOps::concat(
&SqlValue::Varchar(arcstr::ArcStr::from("Hello")),
&SqlValue::Varchar(arcstr::ArcStr::from(" World")),
)
.unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("Hello World")));
}
#[test]
fn test_char_concat() {
let result = StringOps::concat(
&SqlValue::Character(arcstr::ArcStr::from("Hello")),
&SqlValue::Character(arcstr::ArcStr::from(" World")),
)
.unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("Hello World")));
}
#[test]
fn test_mixed_string_concat() {
let result = StringOps::concat(
&SqlValue::Varchar(arcstr::ArcStr::from("Hello")),
&SqlValue::Character(arcstr::ArcStr::from(" World")),
)
.unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("Hello World")));
let result = StringOps::concat(
&SqlValue::Character(arcstr::ArcStr::from("Hello")),
&SqlValue::Varchar(arcstr::ArcStr::from(" World")),
)
.unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("Hello World")));
}
#[test]
fn test_integer_concat() {
let result = StringOps::concat(&SqlValue::Integer(1), &SqlValue::Integer(2)).unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("12")));
let result =
StringOps::concat(&SqlValue::Varchar(arcstr::ArcStr::from("a")), &SqlValue::Integer(1))
.unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("a1")));
let result =
StringOps::concat(&SqlValue::Integer(1), &SqlValue::Varchar(arcstr::ArcStr::from("b")))
.unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("1b")));
}
#[test]
fn test_float_concat() {
let result = StringOps::concat(
&SqlValue::Double(3.15),
&SqlValue::Varchar(arcstr::ArcStr::from("x")),
)
.unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("3.15x")));
let result = StringOps::concat(&SqlValue::Double(10.0), &SqlValue::Double(20.0)).unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("10.020.0")));
}
#[test]
fn test_boolean_concat() {
let result = StringOps::concat(
&SqlValue::Boolean(true),
&SqlValue::Varchar(arcstr::ArcStr::from("x")),
)
.unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("1x")));
let result = StringOps::concat(
&SqlValue::Boolean(false),
&SqlValue::Varchar(arcstr::ArcStr::from("y")),
)
.unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("0y")));
}
#[test]
fn test_other_integer_types() {
let result = StringOps::concat(&SqlValue::Smallint(10), &SqlValue::Smallint(20)).unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("1020")));
let result = StringOps::concat(&SqlValue::Bigint(100), &SqlValue::Bigint(200)).unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("100200")));
let result = StringOps::concat(&SqlValue::Unsigned(5), &SqlValue::Unsigned(6)).unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("56")));
}
}