use crate::errors::ExecutorError;
use crate::evaluator::functions::coercion::{coerce_to_integer, coerce_to_string};
pub(in crate::evaluator::functions) fn substring(
args: &[vibesql_types::SqlValue],
) -> Result<vibesql_types::SqlValue, ExecutorError> {
if args.len() < 2 || args.len() > 3 {
return Err(ExecutorError::WrongNumberOfArguments { function_name: "substr".to_string() });
}
let string_val = &args[0];
let start_val = &args[1];
let length_val = args.get(2);
if matches!(string_val, vibesql_types::SqlValue::Null)
|| matches!(start_val, vibesql_types::SqlValue::Null)
|| (length_val.is_some() && matches!(length_val, Some(vibesql_types::SqlValue::Null)))
{
return Ok(vibesql_types::SqlValue::Null);
}
let start = match coerce_to_integer(start_val) {
Some(n) => n,
None => return Ok(vibesql_types::SqlValue::Null),
};
let length = if let Some(len_val) = length_val { coerce_to_integer(len_val) } else { None };
if let vibesql_types::SqlValue::Blob(bytes) = string_val {
let byte_count = bytes.len() as i64;
let result = sqlite_substr_bytes(bytes, byte_count, start, length);
return Ok(vibesql_types::SqlValue::Blob(result));
}
let s = match coerce_to_string(string_val) {
Some(s) => s,
None => return Ok(vibesql_types::SqlValue::Null),
};
let chars: Vec<char> = s.chars().collect();
let char_count = chars.len() as i64;
let result = sqlite_substr(&chars, char_count, start, length);
Ok(vibesql_types::SqlValue::Varchar(arcstr::ArcStr::from(result)))
}
fn sqlite_substr_bytes(bytes: &[u8], byte_count: i64, start: i64, length: Option<i64>) -> Vec<u8> {
let len = match length {
Some(l) => l,
None => {
if start > 0 {
if start > byte_count {
return Vec::new();
}
let start_idx = (start - 1) as usize;
return bytes[start_idx..].to_vec();
} else if start < 0 {
let start_from_end = (-start) as usize;
if start_from_end > bytes.len() {
return bytes.to_vec();
}
let start_idx = bytes.len() - start_from_end;
return bytes[start_idx..].to_vec();
} else {
return bytes.to_vec();
}
}
};
if len == 0 {
return Vec::new();
}
if len > 0 {
if start > 0 {
if start > byte_count {
return Vec::new();
}
let start_idx = (start - 1) as usize;
let end_idx = std::cmp::min(start_idx + len as usize, bytes.len());
bytes[start_idx..end_idx].to_vec()
} else if start < 0 {
let start_from_end = (-start) as usize;
if start_from_end > bytes.len() {
let bytes_before_data = start_from_end - bytes.len();
if len as usize <= bytes_before_data {
return Vec::new();
}
let effective_len = (len as usize) - bytes_before_data;
return bytes[..std::cmp::min(effective_len, bytes.len())].to_vec();
}
let start_idx = bytes.len() - start_from_end;
let end_idx = std::cmp::min(start_idx + len as usize, bytes.len());
bytes[start_idx..end_idx].to_vec()
} else {
let effective_len = (len - 1) as usize;
if effective_len == 0 {
return Vec::new();
}
bytes[..std::cmp::min(effective_len, bytes.len())].to_vec()
}
} else {
let abs_len = (-len) as usize;
if start > 0 {
let requested_end = (start - 1) as usize;
let requested_start = (start as usize).saturating_sub(abs_len);
if requested_end == 0 {
return Vec::new();
}
let valid_start = std::cmp::max(requested_start, 1);
let valid_end = std::cmp::min(requested_end, bytes.len());
if valid_start > valid_end {
return Vec::new();
}
let start_idx = valid_start - 1;
let end_idx = valid_end;
bytes[start_idx..end_idx].to_vec()
} else if start < 0 {
let start_from_end = (-start) as usize;
if start_from_end > bytes.len() {
return Vec::new();
}
let end_pos = bytes.len() - start_from_end;
let start_pos = end_pos.saturating_sub(abs_len);
bytes[start_pos..end_pos].to_vec()
} else {
Vec::new()
}
}
}
fn sqlite_substr(chars: &[char], char_count: i64, start: i64, length: Option<i64>) -> String {
let len = match length {
Some(l) => l,
None => {
if start > 0 {
if start > char_count {
return String::new();
}
let start_idx = (start - 1) as usize;
return chars[start_idx..].iter().collect();
} else if start < 0 {
let start_from_end = (-start) as usize;
if start_from_end > chars.len() {
return chars.iter().collect();
}
let start_idx = chars.len() - start_from_end;
return chars[start_idx..].iter().collect();
} else {
return chars.iter().collect();
}
}
};
if len == 0 {
return String::new();
}
if len > 0 {
if start > 0 {
if start > char_count {
return String::new();
}
let start_idx = (start - 1) as usize;
let end_idx = std::cmp::min(start_idx + len as usize, chars.len());
chars[start_idx..end_idx].iter().collect()
} else if start < 0 {
let start_from_end = (-start) as usize;
if start_from_end > chars.len() {
let chars_before_string = start_from_end - chars.len();
if len as usize <= chars_before_string {
return String::new();
}
let effective_len = (len as usize) - chars_before_string;
return chars[..std::cmp::min(effective_len, chars.len())].iter().collect();
}
let start_idx = chars.len() - start_from_end;
let end_idx = std::cmp::min(start_idx + len as usize, chars.len());
chars[start_idx..end_idx].iter().collect()
} else {
let effective_len = (len - 1) as usize;
if effective_len == 0 {
return String::new();
}
chars[..std::cmp::min(effective_len, chars.len())].iter().collect()
}
} else {
let abs_len = (-len) as usize;
if start > 0 {
let requested_end = (start - 1) as usize;
let requested_start = (start as usize).saturating_sub(abs_len);
if requested_end == 0 {
return String::new();
}
let valid_start = std::cmp::max(requested_start, 1);
let valid_end = std::cmp::min(requested_end, chars.len());
if valid_start > valid_end {
return String::new();
}
let start_idx = valid_start - 1;
let end_idx = valid_end;
chars[start_idx..end_idx].iter().collect()
} else if start < 0 {
let start_from_end = (-start) as usize;
if start_from_end > chars.len() {
return String::new();
}
let end_pos = chars.len() - start_from_end;
let start_pos = end_pos.saturating_sub(abs_len);
chars[start_pos..end_pos].iter().collect()
} else {
String::new()
}
}
}
pub(in crate::evaluator::functions) fn left(
args: &[vibesql_types::SqlValue],
) -> Result<vibesql_types::SqlValue, ExecutorError> {
if args.len() != 2 {
return Err(ExecutorError::UnsupportedFeature(format!(
"LEFT requires exactly 2 arguments, got {}",
args.len()
)));
}
match (&args[0], &args[1]) {
(vibesql_types::SqlValue::Null, _) | (_, vibesql_types::SqlValue::Null) => {
Ok(vibesql_types::SqlValue::Null)
}
(
vibesql_types::SqlValue::Varchar(s) | vibesql_types::SqlValue::Character(s),
vibesql_types::SqlValue::Integer(n),
) => {
if *n < 0 {
Ok(vibesql_types::SqlValue::Varchar(arcstr::ArcStr::from("")))
} else {
let n_usize = *n as usize;
let result: String = s.chars().take(n_usize).collect();
Ok(vibesql_types::SqlValue::Varchar(arcstr::ArcStr::from(result)))
}
}
(a, b) => Err(ExecutorError::UnsupportedFeature(format!(
"LEFT requires string and integer arguments, got {:?} and {:?}",
a, b
))),
}
}
pub(in crate::evaluator::functions) fn right(
args: &[vibesql_types::SqlValue],
) -> Result<vibesql_types::SqlValue, ExecutorError> {
if args.len() != 2 {
return Err(ExecutorError::UnsupportedFeature(format!(
"RIGHT requires exactly 2 arguments, got {}",
args.len()
)));
}
match (&args[0], &args[1]) {
(vibesql_types::SqlValue::Null, _) | (_, vibesql_types::SqlValue::Null) => {
Ok(vibesql_types::SqlValue::Null)
}
(
vibesql_types::SqlValue::Varchar(s) | vibesql_types::SqlValue::Character(s),
vibesql_types::SqlValue::Integer(n),
) => {
if *n < 0 {
Ok(vibesql_types::SqlValue::Varchar(arcstr::ArcStr::from("")))
} else {
let n_usize = *n as usize;
let char_count = s.chars().count();
if n_usize >= char_count {
Ok(vibesql_types::SqlValue::Varchar(s.clone()))
} else {
let skip_count = char_count - n_usize;
let result: String = s.chars().skip(skip_count).collect();
Ok(vibesql_types::SqlValue::Varchar(arcstr::ArcStr::from(result)))
}
}
}
(a, b) => Err(ExecutorError::UnsupportedFeature(format!(
"RIGHT requires string and integer arguments, got {:?} and {:?}",
a, b
))),
}
}