Transforms a like `pattern` to a regex compatible pattern. To achieve that, it does: 1. Replace `LIKE` multi-character wildcards `%` => `.*` (unless they're at the start or end of the pattern, where the regex is just truncated - e.g. `%foo%` => `foo` rather than `^.*foo.*$`) 2. Replace `LIKE` single-character wildcards `_` => `.` 3. Escape regex meta characters to match them and not be evaluated
(pattern: &str, case_insensitive: bool)
| 245 | /// 3. Escape regex meta characters to match them and not be evaluated as regex special chars. e.g. `.` => `\\.` |
| 246 | /// 4. Replace escaped `LIKE` wildcards removing the escape characters to be able to match it as a regex. e.g. `\\%` => `%` |
| 247 | fn regex_like(pattern: &str, case_insensitive: bool) -> Result<Regex, ArrowError> { |
| 248 | let mut result = String::with_capacity(pattern.len() * 2); |
| 249 | let mut chars_iter = pattern.chars().peekable(); |
| 250 | match chars_iter.peek() { |
| 251 | // if the pattern starts with `%`, we avoid starting the regex with a slow but meaningless `^.*` |
| 252 | Some('%') => { |
| 253 | chars_iter.next(); |
| 254 | } |
| 255 | _ => result.push('^'), |
| 256 | }; |
| 257 | |
| 258 | while let Some(c) = chars_iter.next() { |
| 259 | match c { |
| 260 | '\\' => { |
| 261 | match chars_iter.peek() { |
| 262 | Some(&next) => { |
| 263 | if regex_syntax::is_meta_character(next) { |
| 264 | result.push('\\'); |
| 265 | } |
| 266 | result.push(next); |
| 267 | // Skipping the next char as it is already appended |
| 268 | chars_iter.next(); |
| 269 | } |
| 270 | None => { |
| 271 | // Trailing backslash in the pattern. E.g. PostgreSQL and Trino treat it as an error, but e.g. Snowflake treats it as a literal backslash |
| 272 | result.push('\\'); |
| 273 | result.push('\\'); |
| 274 | } |
| 275 | } |
| 276 | } |
| 277 | '%' => result.push_str(".*"), |
| 278 | '_' => result.push('.'), |
| 279 | c => { |
| 280 | if regex_syntax::is_meta_character(c) { |
| 281 | result.push('\\'); |
| 282 | } |
| 283 | result.push(c); |
| 284 | } |
| 285 | } |
| 286 | } |
| 287 | // instead of ending the regex with `.*$` and making it needlessly slow, we just end the regex |
| 288 | if result.ends_with(".*") { |
| 289 | result.pop(); |
| 290 | result.pop(); |
| 291 | } else { |
| 292 | result.push('$'); |
| 293 | } |
| 294 | RegexBuilder::new(&result) |
| 295 | .case_insensitive(case_insensitive) |
| 296 | .dot_matches_new_line(true) |
| 297 | .build() |
| 298 | .map_err(|e| { |
| 299 | ArrowError::InvalidArgumentError(format!( |
| 300 | "Unable to build regex from LIKE pattern: {e}" |
| 301 | )) |
| 302 | }) |
| 303 | } |
| 304 |