1use std::collections::HashSet;
4
5pub fn like_match(text: &str, pattern: &str) -> bool {
11 let text_chars: Vec<char> = text.chars().collect();
12 let pattern_chars: Vec<char> = pattern.chars().collect();
13
14 like_match_recursive(&text_chars, &pattern_chars, 0, 0)
15}
16
17fn like_match_recursive(
18 text: &[char],
19 pattern: &[char],
20 text_idx: usize,
21 pattern_idx: usize,
22) -> bool {
23 if pattern_idx == pattern.len() {
25 return text_idx == text.len();
26 }
27
28 if pattern[pattern_idx] == '%' {
30 if like_match_recursive(text, pattern, text_idx, pattern_idx + 1) {
32 return true;
33 }
34 if text_idx < text.len() {
36 return like_match_recursive(text, pattern, text_idx + 1, pattern_idx);
37 }
38 return false;
39 }
40
41 if text_idx >= text.len() {
43 return false;
44 }
45
46 if pattern[pattern_idx] == '_' {
48 return like_match_recursive(text, pattern, text_idx + 1, pattern_idx + 1);
49 }
50
51 if text[text_idx] == pattern[pattern_idx] {
53 return like_match_recursive(text, pattern, text_idx + 1, pattern_idx + 1);
54 }
55
56 false
57}
58
59pub fn collect_column_refs(expr: &super::Expr, columns: &mut HashSet<String>) {
61 match expr {
62 super::Expr::Column { name, .. } => {
63 columns.insert(name.clone());
64 }
65 super::Expr::BinaryOp { left, right, .. } => {
66 collect_column_refs(left, columns);
67 collect_column_refs(right, columns);
68 }
69 super::Expr::UnaryOp { expr, .. } => {
70 collect_column_refs(expr, columns);
71 }
72 super::Expr::Function { args, .. } => {
73 for arg in args {
74 collect_column_refs(arg, columns);
75 }
76 }
77 super::Expr::Aggregate { arg, .. } => {
78 if let Some(e) = arg {
79 collect_column_refs(e, columns);
80 }
81 }
82 super::Expr::Case {
83 when_clauses,
84 else_result,
85 ..
86 } => {
87 for (cond, res) in when_clauses {
88 collect_column_refs(cond, columns);
89 collect_column_refs(res, columns);
90 }
91 if let Some(else_expr) = else_result {
92 collect_column_refs(else_expr, columns);
93 }
94 }
95 super::Expr::InList { expr, list, .. } => {
96 collect_column_refs(expr, columns);
97 for item in list {
98 collect_column_refs(item, columns);
99 }
100 }
101 super::Expr::Between {
102 expr, low, high, ..
103 } => {
104 collect_column_refs(expr, columns);
105 collect_column_refs(low, columns);
106 collect_column_refs(high, columns);
107 }
108 super::Expr::Window { .. } => {
109 }
111 super::Expr::ScalarSubquery { .. }
112 | super::Expr::Exists { .. }
113 | super::Expr::InSubquery { .. }
114 | super::Expr::AnySubquery { .. }
115 | super::Expr::AllSubquery { .. } => {
116 }
118 super::Expr::Literal(_)
119 | super::Expr::Parameter { .. }
120 | super::Expr::Wildcard
121 | super::Expr::QualifiedWildcard(_)
122 | super::Expr::Like { .. } => {
123 }
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_like_match_exact() {
134 assert!(like_match("hello", "hello"));
135 assert!(!like_match("hello", "world"));
136 }
137
138 #[test]
139 fn test_like_match_percent() {
140 assert!(like_match("hello", "h%"));
141 assert!(like_match("hello", "%lo"));
142 assert!(like_match("hello", "h%o"));
143 assert!(like_match("hello", "%"));
144 assert!(!like_match("hello", "w%"));
145 }
146
147 #[test]
148 fn test_like_match_underscore() {
149 assert!(like_match("hello", "h_llo"));
150 assert!(like_match("hello", "_ello"));
151 assert!(like_match("hello", "hell_"));
152 assert!(!like_match("hello", "h_lo"));
153 }
154
155 #[test]
156 fn test_like_match_combined() {
157 assert!(like_match("hello world", "h%d"));
158 assert!(like_match("hello world", "h_llo%"));
159 assert!(like_match("hello world", "%o w%"));
160 assert!(!like_match("hello world", "h%x"));
161 }
162
163 #[test]
164 fn test_like_match_edge_cases() {
165 assert!(like_match("", ""));
166 assert!(like_match("", "%"));
167 assert!(!like_match("", "_"));
168 assert!(like_match("a", "_"));
169 assert!(!like_match("ab", "_"));
170 }
171
172 #[test]
173 fn test_collect_column_refs_literal() {
174 use super::super::Expr;
175 use featherdb_core::Value;
176
177 let expr = Expr::Literal(Value::Integer(42));
178 let mut columns = HashSet::new();
179 collect_column_refs(&expr, &mut columns);
180 assert!(columns.is_empty());
181 }
182
183 #[test]
184 fn test_collect_column_refs_column() {
185 use super::super::Expr;
186
187 let expr = Expr::Column {
188 table: None,
189 name: "age".to_string(),
190 index: None,
191 };
192 let mut columns = HashSet::new();
193 collect_column_refs(&expr, &mut columns);
194 assert_eq!(columns.len(), 1);
195 assert!(columns.contains("age"));
196 }
197
198 #[test]
199 fn test_collect_column_refs_binary_op() {
200 use super::super::{BinaryOp, Expr};
201
202 let expr = Expr::BinaryOp {
203 left: Box::new(Expr::Column {
204 table: None,
205 name: "age".to_string(),
206 index: None,
207 }),
208 op: BinaryOp::Gt,
209 right: Box::new(Expr::Column {
210 table: None,
211 name: "salary".to_string(),
212 index: None,
213 }),
214 };
215 let mut columns = HashSet::new();
216 collect_column_refs(&expr, &mut columns);
217 assert_eq!(columns.len(), 2);
218 assert!(columns.contains("age"));
219 assert!(columns.contains("salary"));
220 }
221
222 #[test]
223 fn test_collect_column_refs_qualified_column() {
224 use super::super::Expr;
225
226 let expr = Expr::Column {
227 table: Some("users".to_string()),
228 name: "name".to_string(),
229 index: None,
230 };
231 let mut columns = HashSet::new();
232 collect_column_refs(&expr, &mut columns);
233 assert_eq!(columns.len(), 1);
234 assert!(columns.contains("name"));
235 }
236}