use std::borrow::Cow;
use vibesql_types::SqlValue;
use crate::errors::ExecutorError;
pub(crate) fn like(args: &[SqlValue]) -> Result<SqlValue, ExecutorError> {
if args.len() < 2 || args.len() > 3 {
return Err(ExecutorError::UnsupportedFeature(format!(
"LIKE requires 2 or 3 arguments, got {}",
args.len()
)));
}
if matches!(args[0], SqlValue::Null) || matches!(args[1], SqlValue::Null) {
return Ok(SqlValue::Null);
}
let pattern = match &args[0] {
SqlValue::Varchar(s) | SqlValue::Character(s) => s.as_str(),
other => {
return Err(ExecutorError::UnsupportedFeature(format!(
"LIKE pattern must be a string, got {:?}",
other
)));
}
};
let text: Cow<str> = match &args[1] {
SqlValue::Varchar(s) | SqlValue::Character(s) => Cow::Borrowed(s.as_str()),
SqlValue::Integer(i) => Cow::Owned(i.to_string()),
SqlValue::Bigint(i) => Cow::Owned(i.to_string()),
SqlValue::Smallint(i) => Cow::Owned(i.to_string()),
SqlValue::Unsigned(u) => Cow::Owned(u.to_string()),
SqlValue::Real(r) => Cow::Owned(r.to_string()),
SqlValue::Double(d) => Cow::Owned(d.to_string()),
SqlValue::Numeric(n) => Cow::Owned(n.to_string()),
SqlValue::Float(f) => Cow::Owned(f.to_string()),
SqlValue::Boolean(b) => Cow::Owned(if *b { "1".to_string() } else { "0".to_string() }),
other => Cow::Owned(other.to_string()),
};
let escape_char: Option<char> = if args.len() >= 3 {
match &args[2] {
SqlValue::Null => return Ok(SqlValue::Null),
SqlValue::Varchar(s) | SqlValue::Character(s) => {
let mut chars = s.chars();
match (chars.next(), chars.next()) {
(Some(c), None) => Some(c), _ => {
return Err(ExecutorError::SqliteCompatError(
"ESCAPE expression must be a single character".to_string(),
));
}
}
}
_ => {
return Err(ExecutorError::SqliteCompatError(
"ESCAPE expression must be a single character".to_string(),
));
}
}
} else {
None
};
let matched = crate::evaluator::pattern::like_match(&text, pattern, false, escape_char);
Ok(SqlValue::Integer(if matched { 1 } else { 0 }))
}
pub(crate) fn glob(args: &[SqlValue]) -> Result<SqlValue, ExecutorError> {
if args.len() != 2 {
return Err(ExecutorError::UnsupportedFeature(format!(
"GLOB requires exactly 2 arguments, got {}",
args.len()
)));
}
if matches!(args[0], SqlValue::Null) || matches!(args[1], SqlValue::Null) {
return Ok(SqlValue::Null);
}
let pattern = match &args[0] {
SqlValue::Varchar(s) | SqlValue::Character(s) => s.as_str(),
other => {
return Err(ExecutorError::UnsupportedFeature(format!(
"GLOB pattern must be a string, got {:?}",
other
)));
}
};
let text: Cow<str> = match &args[1] {
SqlValue::Varchar(s) | SqlValue::Character(s) => Cow::Borrowed(s.as_str()),
SqlValue::Integer(i) => Cow::Owned(i.to_string()),
SqlValue::Bigint(i) => Cow::Owned(i.to_string()),
SqlValue::Smallint(i) => Cow::Owned(i.to_string()),
SqlValue::Unsigned(u) => Cow::Owned(u.to_string()),
SqlValue::Real(r) => Cow::Owned(r.to_string()),
SqlValue::Double(d) => Cow::Owned(d.to_string()),
SqlValue::Numeric(n) => Cow::Owned(n.to_string()),
SqlValue::Float(f) => Cow::Owned(f.to_string()),
SqlValue::Boolean(b) => Cow::Owned(if *b { "1".to_string() } else { "0".to_string() }),
other => Cow::Owned(other.to_string()),
};
let matched = crate::evaluator::pattern::glob_match(&text, pattern);
Ok(SqlValue::Integer(if matched { 1 } else { 0 }))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_like() {
assert_eq!(
like(&[SqlValue::Varchar("%ello".into()), SqlValue::Varchar("hello".into())]).unwrap(),
SqlValue::Integer(1)
);
assert_eq!(
like(&[SqlValue::Varchar("h_llo".into()), SqlValue::Varchar("hello".into())]).unwrap(),
SqlValue::Integer(1)
);
assert_eq!(
like(&[SqlValue::Varchar("h%o".into()), SqlValue::Varchar("hello".into())]).unwrap(),
SqlValue::Integer(1)
);
assert_eq!(
like(&[SqlValue::Varchar("HELLO".into()), SqlValue::Varchar("hello".into())]).unwrap(),
SqlValue::Integer(1)
);
assert_eq!(
like(&[SqlValue::Varchar("hello".into()), SqlValue::Varchar("HELLO".into())]).unwrap(),
SqlValue::Integer(1)
);
assert_eq!(
like(&[SqlValue::Varchar("abc".into()), SqlValue::Varchar("abc".into())]).unwrap(),
SqlValue::Integer(1)
);
assert_eq!(
like(&[SqlValue::Varchar("___".into()), SqlValue::Varchar("ab".into())]).unwrap(),
SqlValue::Integer(0)
);
assert_eq!(
like(&[SqlValue::Null, SqlValue::Varchar("hello".into())]).unwrap(),
SqlValue::Null
);
assert_eq!(
like(&[SqlValue::Varchar("hello".into()), SqlValue::Null]).unwrap(),
SqlValue::Null
);
assert!(like(&[]).is_err());
assert!(like(&[SqlValue::Varchar("a".into())]).is_err());
}
#[test]
fn test_glob() {
assert_eq!(
glob(&[SqlValue::Varchar("*ello".into()), SqlValue::Varchar("hello".into())]).unwrap(),
SqlValue::Integer(1)
);
assert_eq!(
glob(&[SqlValue::Varchar("h?llo".into()), SqlValue::Varchar("hello".into())]).unwrap(),
SqlValue::Integer(1)
);
assert_eq!(
glob(&[SqlValue::Varchar("h*o".into()), SqlValue::Varchar("hello".into())]).unwrap(),
SqlValue::Integer(1)
);
assert_eq!(
glob(&[SqlValue::Varchar("abc".into()), SqlValue::Varchar("abc".into())]).unwrap(),
SqlValue::Integer(1)
);
assert_eq!(
glob(&[SqlValue::Varchar("abc".into()), SqlValue::Varchar("ABC".into())]).unwrap(),
SqlValue::Integer(0)
);
assert_eq!(
glob(&[SqlValue::Varchar("[abc]".into()), SqlValue::Varchar("a".into())]).unwrap(),
SqlValue::Integer(1)
);
assert_eq!(
glob(&[SqlValue::Varchar("[abc]".into()), SqlValue::Varchar("d".into())]).unwrap(),
SqlValue::Integer(0)
);
assert_eq!(
glob(&[SqlValue::Varchar("[a-z]".into()), SqlValue::Varchar("m".into())]).unwrap(),
SqlValue::Integer(1)
);
assert_eq!(
glob(&[SqlValue::Varchar("[^abc]".into()), SqlValue::Varchar("d".into())]).unwrap(),
SqlValue::Integer(1)
);
assert_eq!(
glob(&[SqlValue::Varchar("[^abc]".into()), SqlValue::Varchar("a".into())]).unwrap(),
SqlValue::Integer(0)
);
assert_eq!(
glob(&[SqlValue::Varchar("???".into()), SqlValue::Varchar("ab".into())]).unwrap(),
SqlValue::Integer(0)
);
assert_eq!(
glob(&[SqlValue::Null, SqlValue::Varchar("hello".into())]).unwrap(),
SqlValue::Null
);
assert_eq!(
glob(&[SqlValue::Varchar("hello".into()), SqlValue::Null]).unwrap(),
SqlValue::Null
);
assert!(glob(&[]).is_err());
assert!(glob(&[SqlValue::Varchar("a".into())]).is_err());
}
}