pub fn batch_string_eq(
values: &[std::sync::Arc<str>],
nulls: Option<&[bool]>,
target: &str,
) -> Vec<bool> {
let target_len = target.len();
let mut result = Vec::with_capacity(values.len());
for (i, value) in values.iter().enumerate() {
if let Some(null_mask) = nulls {
if null_mask[i] {
result.push(false);
continue;
}
}
if value.len() != target_len {
result.push(false);
} else {
result.push(&**value == target);
}
}
result
}
pub fn batch_string_ne(
values: &[std::sync::Arc<str>],
nulls: Option<&[bool]>,
target: &str,
) -> Vec<bool> {
let target_len = target.len();
let mut result = Vec::with_capacity(values.len());
for (i, value) in values.iter().enumerate() {
if let Some(null_mask) = nulls {
if null_mask[i] {
result.push(false);
continue;
}
}
if value.len() != target_len {
result.push(true);
} else {
result.push(&**value != target);
}
}
result
}
#[inline]
fn ascii_eq_ignore_case(a: &str, b: &str) -> bool {
a.len() == b.len() && a.bytes().zip(b.bytes()).all(|(ac, bc)| ac.eq_ignore_ascii_case(&bc))
}
#[inline]
fn ascii_starts_with_ignore_case(text: &str, prefix: &str) -> bool {
if text.len() < prefix.len() {
return false;
}
ascii_eq_ignore_case(&text[..prefix.len()], prefix)
}
#[inline]
fn ascii_ends_with_ignore_case(text: &str, suffix: &str) -> bool {
if text.len() < suffix.len() {
return false;
}
ascii_eq_ignore_case(&text[text.len() - suffix.len()..], suffix)
}
#[inline]
fn ascii_contains_ignore_case(text: &str, substring: &str) -> bool {
if substring.is_empty() {
return true;
}
if text.len() < substring.len() {
return false;
}
for i in 0..=(text.len() - substring.len()) {
if ascii_eq_ignore_case(&text[i..i + substring.len()], substring) {
return true;
}
}
false
}
pub fn batch_string_starts_with(
values: &[std::sync::Arc<str>],
nulls: Option<&[bool]>,
prefix: &str,
) -> Vec<bool> {
let mut result = Vec::with_capacity(values.len());
for (i, value) in values.iter().enumerate() {
if let Some(null_mask) = nulls {
if null_mask[i] {
result.push(false);
continue;
}
}
result.push(ascii_starts_with_ignore_case(value, prefix));
}
result
}
pub fn batch_string_ends_with(
values: &[std::sync::Arc<str>],
nulls: Option<&[bool]>,
suffix: &str,
) -> Vec<bool> {
let mut result = Vec::with_capacity(values.len());
for (i, value) in values.iter().enumerate() {
if let Some(null_mask) = nulls {
if null_mask[i] {
result.push(false);
continue;
}
}
result.push(ascii_ends_with_ignore_case(value, suffix));
}
result
}
pub fn batch_string_contains(
values: &[std::sync::Arc<str>],
nulls: Option<&[bool]>,
substring: &str,
) -> Vec<bool> {
let mut result = Vec::with_capacity(values.len());
for (i, value) in values.iter().enumerate() {
if let Some(null_mask) = nulls {
if null_mask[i] {
result.push(false);
continue;
}
}
result.push(ascii_contains_ignore_case(value, substring));
}
result
}
#[derive(Debug, Clone)]
pub enum LikePattern {
Exact(String),
Prefix(String),
Suffix(String),
Contains(String),
PrefixSuffix { prefix: String, suffix: String },
General(String),
}
impl LikePattern {
pub fn parse(pattern: &str) -> Self {
if pattern.contains('_') {
return LikePattern::General(pattern.to_string());
}
let percent_count = pattern.matches('%').count();
match percent_count {
0 => {
LikePattern::Exact(pattern.to_string())
}
1 => {
if pattern.starts_with('%') && pattern.ends_with('%') && pattern.len() > 1 {
if pattern == "%" {
LikePattern::General(pattern.to_string())
} else {
let inner = &pattern[1..pattern.len() - 1];
if inner.is_empty() || inner.contains('%') {
LikePattern::General(pattern.to_string())
} else {
LikePattern::Contains(inner.to_string())
}
}
} else if let Some(prefix) = pattern.strip_suffix('%') {
LikePattern::Prefix(prefix.to_string())
} else if let Some(suffix) = pattern.strip_prefix('%') {
LikePattern::Suffix(suffix.to_string())
} else {
let parts: Vec<&str> = pattern.split('%').collect();
if parts.len() == 2 && !parts[0].is_empty() && !parts[1].is_empty() {
LikePattern::PrefixSuffix {
prefix: parts[0].to_string(),
suffix: parts[1].to_string(),
}
} else {
LikePattern::General(pattern.to_string())
}
}
}
2 => {
if pattern.starts_with('%') && pattern.ends_with('%') {
let inner = &pattern[1..pattern.len() - 1];
if !inner.contains('%') {
LikePattern::Contains(inner.to_string())
} else {
LikePattern::General(pattern.to_string())
}
} else if !pattern.starts_with('%') && !pattern.ends_with('%') {
LikePattern::General(pattern.to_string())
} else if pattern.starts_with('%') {
LikePattern::General(pattern.to_string())
} else if pattern.ends_with('%') {
LikePattern::General(pattern.to_string())
} else {
let parts: Vec<&str> = pattern.split('%').collect();
if parts.len() == 2 && !parts[0].is_empty() && !parts[1].is_empty() {
LikePattern::PrefixSuffix {
prefix: parts[0].to_string(),
suffix: parts[1].to_string(),
}
} else {
LikePattern::General(pattern.to_string())
}
}
}
_ => {
LikePattern::General(pattern.to_string())
}
}
}
}
pub fn batch_string_like(
values: &[std::sync::Arc<str>],
nulls: Option<&[bool]>,
pattern: &LikePattern,
) -> Vec<bool> {
match pattern {
LikePattern::Exact(s) => batch_string_eq_ignore_case(values, nulls, s),
LikePattern::Prefix(prefix) => batch_string_starts_with(values, nulls, prefix),
LikePattern::Suffix(suffix) => batch_string_ends_with(values, nulls, suffix),
LikePattern::Contains(substring) => batch_string_contains(values, nulls, substring),
LikePattern::PrefixSuffix { prefix, suffix } => {
batch_string_prefix_suffix(values, nulls, prefix, suffix)
}
LikePattern::General(pattern) => batch_string_like_general(values, nulls, pattern),
}
}
fn batch_string_eq_ignore_case(
values: &[std::sync::Arc<str>],
nulls: Option<&[bool]>,
target: &str,
) -> Vec<bool> {
let mut result = Vec::with_capacity(values.len());
for (i, value) in values.iter().enumerate() {
if let Some(null_mask) = nulls {
if null_mask[i] {
result.push(false);
continue;
}
}
result.push(ascii_eq_ignore_case(value, target));
}
result
}
fn batch_string_prefix_suffix(
values: &[std::sync::Arc<str>],
nulls: Option<&[bool]>,
prefix: &str,
suffix: &str,
) -> Vec<bool> {
let min_len = prefix.len() + suffix.len();
let mut result = Vec::with_capacity(values.len());
for (i, value) in values.iter().enumerate() {
if let Some(null_mask) = nulls {
if null_mask[i] {
result.push(false);
continue;
}
}
if value.len() < min_len {
result.push(false);
} else {
result.push(
ascii_starts_with_ignore_case(value, prefix)
&& ascii_ends_with_ignore_case(value, suffix),
);
}
}
result
}
fn batch_string_like_general(
values: &[std::sync::Arc<str>],
nulls: Option<&[bool]>,
pattern: &str,
) -> Vec<bool> {
let mut result = Vec::with_capacity(values.len());
for (i, value) in values.iter().enumerate() {
if let Some(null_mask) = nulls {
if null_mask[i] {
result.push(false);
continue;
}
}
result.push(like_match(value, pattern));
}
result
}
fn like_match(text: &str, pattern: &str) -> bool {
let text_chars: Vec<char> = text.chars().collect();
let pattern_chars: Vec<char> = pattern.chars().collect();
let m = text_chars.len();
let n = pattern_chars.len();
let mut dp = vec![vec![false; n + 1]; m + 1];
dp[0][0] = true;
for j in 1..=n {
if pattern_chars[j - 1] == '%' {
dp[0][j] = dp[0][j - 1];
}
}
for i in 1..=m {
for j in 1..=n {
let pc = pattern_chars[j - 1];
let tc = text_chars[i - 1];
if pc == '%' {
dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
} else if pc == '_' {
dp[i][j] = dp[i - 1][j - 1];
} else {
let matches = if pc.is_ascii_alphabetic() && tc.is_ascii_alphabetic() {
pc.eq_ignore_ascii_case(&tc)
} else {
pc == tc
};
if matches {
dp[i][j] = dp[i - 1][j - 1];
}
}
}
}
dp[m][n]
}
pub fn batch_string_lt(
values: &[std::sync::Arc<str>],
nulls: Option<&[bool]>,
target: &str,
) -> Vec<bool> {
let mut result = Vec::with_capacity(values.len());
for (i, value) in values.iter().enumerate() {
if let Some(null_mask) = nulls {
if null_mask[i] {
result.push(false);
continue;
}
}
result.push(&**value < target);
}
result
}
pub fn batch_string_gt(
values: &[std::sync::Arc<str>],
nulls: Option<&[bool]>,
target: &str,
) -> Vec<bool> {
let mut result = Vec::with_capacity(values.len());
for (i, value) in values.iter().enumerate() {
if let Some(null_mask) = nulls {
if null_mask[i] {
result.push(false);
continue;
}
}
result.push(&**value > target);
}
result
}
pub fn batch_string_le(
values: &[std::sync::Arc<str>],
nulls: Option<&[bool]>,
target: &str,
) -> Vec<bool> {
let mut result = Vec::with_capacity(values.len());
for (i, value) in values.iter().enumerate() {
if let Some(null_mask) = nulls {
if null_mask[i] {
result.push(false);
continue;
}
}
result.push(&**value <= target);
}
result
}
pub fn batch_string_ge(
values: &[std::sync::Arc<str>],
nulls: Option<&[bool]>,
target: &str,
) -> Vec<bool> {
let mut result = Vec::with_capacity(values.len());
for (i, value) in values.iter().enumerate() {
if let Some(null_mask) = nulls {
if null_mask[i] {
result.push(false);
continue;
}
}
result.push(&**value >= target);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_batch_string_eq() {
let values = vec![
std::sync::Arc::from("apple"),
std::sync::Arc::from("banana"),
std::sync::Arc::from("apple"),
std::sync::Arc::from("cherry"),
];
let result = batch_string_eq(&values, None, "apple");
assert_eq!(result, vec![true, false, true, false]);
}
#[test]
fn test_batch_string_eq_with_nulls() {
let values = vec![
std::sync::Arc::from("apple"),
std::sync::Arc::from("banana"),
std::sync::Arc::from("apple"),
std::sync::Arc::from("cherry"),
];
let nulls = Some(vec![false, false, true, false]);
let result = batch_string_eq(&values, nulls.as_deref(), "apple");
assert_eq!(result, vec![true, false, false, false]);
}
#[test]
fn test_batch_string_ne() {
let values = vec![
std::sync::Arc::from("apple"),
std::sync::Arc::from("banana"),
std::sync::Arc::from("apple"),
];
let result = batch_string_ne(&values, None, "apple");
assert_eq!(result, vec![false, true, false]);
}
#[test]
fn test_batch_string_starts_with() {
let values = vec![
std::sync::Arc::from("apple"),
std::sync::Arc::from("apricot"),
std::sync::Arc::from("banana"),
std::sync::Arc::from("app"),
];
let result = batch_string_starts_with(&values, None, "app");
assert_eq!(result, vec![true, false, false, true]);
}
#[test]
fn test_batch_string_ends_with() {
let values = vec![
std::sync::Arc::from("apple"),
std::sync::Arc::from("pineapple"),
std::sync::Arc::from("banana"),
std::sync::Arc::from("le"),
];
let result = batch_string_ends_with(&values, None, "le");
assert_eq!(result, vec![true, true, false, true]);
}
#[test]
fn test_batch_string_contains() {
let values = vec![
std::sync::Arc::from("apple"),
std::sync::Arc::from("pineapple"),
std::sync::Arc::from("banana"),
std::sync::Arc::from("application"),
];
let result = batch_string_contains(&values, None, "app");
assert_eq!(result, vec![true, true, false, true]);
}
#[test]
fn test_like_pattern_parse() {
assert!(matches!(LikePattern::parse("apple"), LikePattern::Exact(_)));
assert!(matches!(LikePattern::parse("app%"), LikePattern::Prefix(_)));
assert!(matches!(LikePattern::parse("%le"), LikePattern::Suffix(_)));
assert!(matches!(LikePattern::parse("%app%"), LikePattern::Contains(_)));
assert!(matches!(LikePattern::parse("a%e"), LikePattern::PrefixSuffix { .. }));
assert!(matches!(LikePattern::parse("a_ple"), LikePattern::General(_)));
}
#[test]
fn test_batch_string_like_prefix() {
let values = vec![
std::sync::Arc::from("apple"),
std::sync::Arc::from("apricot"),
std::sync::Arc::from("banana"),
];
let pattern = LikePattern::parse("ap%");
let result = batch_string_like(&values, None, &pattern);
assert_eq!(result, vec![true, true, false]);
}
#[test]
fn test_batch_string_like_suffix() {
let values = vec![
std::sync::Arc::from("apple"),
std::sync::Arc::from("pineapple"),
std::sync::Arc::from("banana"),
];
let pattern = LikePattern::parse("%ple");
let result = batch_string_like(&values, None, &pattern);
assert_eq!(result, vec![true, true, false]);
}
#[test]
fn test_batch_string_like_contains() {
let values = vec![
std::sync::Arc::from("apple"),
std::sync::Arc::from("pineapple"),
std::sync::Arc::from("banana"),
];
let pattern = LikePattern::parse("%app%");
let result = batch_string_like(&values, None, &pattern);
assert_eq!(result, vec![true, true, false]);
}
#[test]
fn test_batch_string_like_prefix_suffix() {
let values = vec![
std::sync::Arc::from("apple"),
std::sync::Arc::from("ample"),
std::sync::Arc::from("banana"),
std::sync::Arc::from("ale"),
];
let pattern = LikePattern::parse("a%le");
let result = batch_string_like(&values, None, &pattern);
assert_eq!(result, vec![true, true, false, true]);
}
#[test]
fn test_like_match_underscore() {
assert!(like_match("apple", "appl_"));
assert!(like_match("apply", "appl_"));
assert!(!like_match("appl", "appl_"));
assert!(!like_match("applee", "appl_"));
assert!(like_match("abc", "a_c"));
assert!(like_match("axc", "a_c"));
assert!(!like_match("ac", "a_c"));
assert!(!like_match("abbc", "a_c"));
}
#[test]
fn test_like_match_percent() {
assert!(like_match("apple", "app%"));
assert!(like_match("app", "app%"));
assert!(!like_match("ap", "app%"));
assert!(like_match("apple", "%ple"));
assert!(like_match("ple", "%ple"));
assert!(!like_match("pl", "%ple"));
assert!(like_match("pineapple", "%app%"));
assert!(like_match("apple", "%app%"));
assert!(like_match("app", "%app%"));
}
#[test]
fn test_like_match_complex() {
assert!(like_match("apple", "a%e"));
assert!(like_match("axe", "a%e"));
assert!(like_match("ae", "a%e"));
assert!(!like_match("axf", "a%e"));
assert!(like_match("apple", "a_p%"));
assert!(like_match("axppp", "a_p%"));
assert!(like_match("appp", "a_p%")); assert!(!like_match("ap", "a_p%")); }
#[test]
fn test_batch_string_comparisons() {
let values = vec![
std::sync::Arc::from("apple"),
std::sync::Arc::from("banana"),
std::sync::Arc::from("cherry"),
];
let result = batch_string_lt(&values, None, "banana");
assert_eq!(result, vec![true, false, false]);
let result = batch_string_gt(&values, None, "banana");
assert_eq!(result, vec![false, false, true]);
let result = batch_string_le(&values, None, "banana");
assert_eq!(result, vec![true, true, false]);
let result = batch_string_ge(&values, None, "banana");
assert_eq!(result, vec![false, true, true]);
}
#[test]
fn test_empty_values() {
let values: Vec<std::sync::Arc<str>> = vec![];
let result = batch_string_eq(&values, None, "test");
assert!(result.is_empty());
let pattern = LikePattern::parse("test%");
let result = batch_string_like(&values, None, &pattern);
assert!(result.is_empty());
}
#[test]
fn test_empty_pattern() {
let values: Vec<std::sync::Arc<str>> =
vec![std::sync::Arc::from(""), std::sync::Arc::from("a"), std::sync::Arc::from("")];
let result = batch_string_eq(&values, None, "");
assert_eq!(result, vec![true, false, true]);
let pattern = LikePattern::parse("%");
let result = batch_string_like(&values, None, &pattern);
assert_eq!(result, vec![true, true, true]);
}
}