Skip to main content

nodedb_query/scan_filter/
like.rs

1// SPDX-License-Identifier: Apache-2.0
2
3/// SQL LIKE pattern matching.
4///
5/// Supports `%` (zero or more characters) and `_` (exactly one character).
6/// When `case_insensitive` is true, both input and pattern are lowercased (ILIKE).
7pub fn sql_like_match(input: &str, pattern: &str, case_insensitive: bool) -> bool {
8    let (input, pattern) = if case_insensitive {
9        (input.to_lowercase(), pattern.to_lowercase())
10    } else {
11        (input.to_string(), pattern.to_string())
12    };
13
14    let input = input.as_bytes();
15    let pattern = pattern.as_bytes();
16
17    let (mut i, mut j) = (0usize, 0usize);
18    let (mut star_j, mut star_i) = (usize::MAX, 0usize);
19
20    while i < input.len() {
21        if j < pattern.len() && (pattern[j] == b'_' || pattern[j] == input[i]) {
22            i += 1;
23            j += 1;
24        } else if j < pattern.len() && pattern[j] == b'%' {
25            star_j = j;
26            star_i = i;
27            j += 1;
28        } else if star_j != usize::MAX {
29            star_i += 1;
30            i = star_i;
31            j = star_j + 1;
32        } else {
33            return false;
34        }
35    }
36
37    while j < pattern.len() && pattern[j] == b'%' {
38        j += 1;
39    }
40
41    j == pattern.len()
42}
43
44#[cfg(test)]
45mod tests {
46    use super::sql_like_match;
47
48    #[test]
49    fn like_basic() {
50        assert!(sql_like_match("hello world", "%world", false));
51        assert!(sql_like_match("hello world", "hello%", false));
52        assert!(!sql_like_match("hello world", "xyz%", false));
53    }
54
55    #[test]
56    fn ilike_case_insensitive() {
57        assert!(sql_like_match("Hello", "hello", true));
58        assert!(sql_like_match("WORLD", "%world%", true));
59    }
60}