use crate::errors::ExecutorError;
use crate::evaluator::functions::coercion::coerce_to_string;
pub(in crate::evaluator::functions) fn trim(
args: &[vibesql_types::SqlValue],
) -> Result<vibesql_types::SqlValue, ExecutorError> {
if args.is_empty() || args.len() > 2 {
return Err(ExecutorError::UnsupportedFeature(format!(
"TRIM requires 1 or 2 arguments, got {}",
args.len()
)));
}
let s = match coerce_to_string(&args[0]) {
Some(s) => s,
None => return Ok(vibesql_types::SqlValue::Null),
};
if args.len() == 1 {
Ok(vibesql_types::SqlValue::Varchar(arcstr::ArcStr::from(s.trim().to_string())))
} else {
let chars_to_remove = match coerce_to_string(&args[1]) {
Some(c) => c,
None => return Ok(vibesql_types::SqlValue::Null),
};
let char_set: std::collections::HashSet<char> = chars_to_remove.chars().collect();
let result = s.trim_matches(|c| char_set.contains(&c)).to_string();
Ok(vibesql_types::SqlValue::Varchar(arcstr::ArcStr::from(result)))
}
}
pub(in crate::evaluator::functions) fn ltrim(
args: &[vibesql_types::SqlValue],
) -> Result<vibesql_types::SqlValue, ExecutorError> {
if args.is_empty() || args.len() > 2 {
return Err(ExecutorError::UnsupportedFeature(format!(
"LTRIM requires 1 or 2 arguments, got {}",
args.len()
)));
}
let s = match coerce_to_string(&args[0]) {
Some(s) => s,
None => return Ok(vibesql_types::SqlValue::Null),
};
if args.len() == 1 {
Ok(vibesql_types::SqlValue::Varchar(arcstr::ArcStr::from(s.trim_start().to_string())))
} else {
let chars_to_remove = match coerce_to_string(&args[1]) {
Some(c) => c,
None => return Ok(vibesql_types::SqlValue::Null),
};
let char_set: std::collections::HashSet<char> = chars_to_remove.chars().collect();
let result = s.trim_start_matches(|c| char_set.contains(&c)).to_string();
Ok(vibesql_types::SqlValue::Varchar(arcstr::ArcStr::from(result)))
}
}
pub(in crate::evaluator::functions) fn rtrim(
args: &[vibesql_types::SqlValue],
) -> Result<vibesql_types::SqlValue, ExecutorError> {
if args.is_empty() || args.len() > 2 {
return Err(ExecutorError::UnsupportedFeature(format!(
"RTRIM requires 1 or 2 arguments, got {}",
args.len()
)));
}
let s = match coerce_to_string(&args[0]) {
Some(s) => s,
None => return Ok(vibesql_types::SqlValue::Null),
};
if args.len() == 1 {
Ok(vibesql_types::SqlValue::Varchar(arcstr::ArcStr::from(s.trim_end().to_string())))
} else {
let chars_to_remove = match coerce_to_string(&args[1]) {
Some(c) => c,
None => return Ok(vibesql_types::SqlValue::Null),
};
let char_set: std::collections::HashSet<char> = chars_to_remove.chars().collect();
let result = s.trim_end_matches(|c| char_set.contains(&c)).to_string();
Ok(vibesql_types::SqlValue::Varchar(arcstr::ArcStr::from(result)))
}
}
#[allow(dead_code)] pub(crate) fn trim_advanced(
string_val: vibesql_types::SqlValue,
position: Option<vibesql_ast::TrimPosition>,
removal_char: Option<vibesql_types::SqlValue>,
) -> Result<vibesql_types::SqlValue, ExecutorError> {
let s = match coerce_to_string(&string_val) {
Some(s) => s,
None => return Ok(vibesql_types::SqlValue::Null),
};
let char_to_remove = if let Some(removal) = removal_char {
match removal {
vibesql_types::SqlValue::Null => return Ok(vibesql_types::SqlValue::Null),
vibesql_types::SqlValue::Varchar(c) | vibesql_types::SqlValue::Character(c) => {
if c.len() != 1 {
return Err(ExecutorError::UnsupportedFeature(format!(
"TRIM removal character must be single character, got '{}'",
c
)));
}
c.chars().next().unwrap()
}
_ => {
return Err(ExecutorError::UnsupportedFeature(format!(
"TRIM removal character must be string, got {:?}",
removal
)))
}
}
} else {
' ' };
let result = match position.unwrap_or(vibesql_ast::TrimPosition::Both) {
vibesql_ast::TrimPosition::Both => s.trim_matches(char_to_remove).to_string(),
vibesql_ast::TrimPosition::Leading => s.trim_start_matches(char_to_remove).to_string(),
vibesql_ast::TrimPosition::Trailing => s.trim_end_matches(char_to_remove).to_string(),
};
Ok(vibesql_types::SqlValue::Varchar(arcstr::ArcStr::from(result)))
}
#[cfg(test)]
mod tests {
use super::*;
use vibesql_types::SqlValue;
#[test]
fn test_trim_whitespace() {
let args = vec![SqlValue::Varchar(arcstr::ArcStr::from(" hello "))];
let result = trim(&args).unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("hello")));
}
#[test]
fn test_trim_with_chars() {
let args = vec![
SqlValue::Varchar(arcstr::ArcStr::from("xxxhelloxxx")),
SqlValue::Varchar(arcstr::ArcStr::from("x")),
];
let result = trim(&args).unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("hello")));
}
#[test]
fn test_trim_with_multiple_chars() {
let args = vec![
SqlValue::Varchar(arcstr::ArcStr::from("abchelloabc")),
SqlValue::Varchar(arcstr::ArcStr::from("abc")),
];
let result = trim(&args).unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("hello")));
}
#[test]
fn test_ltrim_whitespace() {
let args = vec![SqlValue::Varchar(arcstr::ArcStr::from(" hello"))];
let result = ltrim(&args).unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("hello")));
}
#[test]
fn test_ltrim_with_chars() {
let args = vec![
SqlValue::Varchar(arcstr::ArcStr::from("xxxhello")),
SqlValue::Varchar(arcstr::ArcStr::from("x")),
];
let result = ltrim(&args).unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("hello")));
}
#[test]
fn test_rtrim_whitespace() {
let args = vec![SqlValue::Varchar(arcstr::ArcStr::from("hello "))];
let result = rtrim(&args).unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("hello")));
}
#[test]
fn test_rtrim_with_chars() {
let args = vec![
SqlValue::Varchar(arcstr::ArcStr::from("helloxxx")),
SqlValue::Varchar(arcstr::ArcStr::from("x")),
];
let result = rtrim(&args).unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("hello")));
}
#[test]
fn test_trim_null_input() {
let args = vec![SqlValue::Null];
let result = trim(&args).unwrap();
assert_eq!(result, SqlValue::Null);
}
#[test]
fn test_trim_null_chars() {
let args = vec![SqlValue::Varchar(arcstr::ArcStr::from("hello")), SqlValue::Null];
let result = trim(&args).unwrap();
assert_eq!(result, SqlValue::Null);
}
#[test]
fn test_trim_numeric_coercion() {
let args = vec![SqlValue::Integer(123)];
let result = trim(&args).unwrap();
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("123")));
}
}