1use crate::query_plan::pipeline::ASTTransformer;
34use crate::sql::parser::ast::{
35 CTEType, Condition, OrderByItem, SelectItem, SelectStatement, SimpleWhenBranch, SqlExpression,
36 WhenBranch, WhereClause, CTE,
37};
38use anyhow::Result;
39use tracing::debug;
40
41pub struct ILikeToLikeTransformer;
43
44impl ILikeToLikeTransformer {
45 pub fn new() -> Self {
46 Self
47 }
48
49 fn transform_expression(&self, expr: SqlExpression) -> SqlExpression {
51 match expr {
52 SqlExpression::BinaryOp { left, op, right } if op == "ILIKE" => {
54 debug!("Transforming ILIKE to UPPER() LIKE UPPER()");
55
56 SqlExpression::BinaryOp {
57 left: Box::new(SqlExpression::FunctionCall {
58 name: "UPPER".to_string(),
59 args: vec![self.transform_expression(*left)],
60 distinct: false,
61 }),
62 op: "LIKE".to_string(),
63 right: Box::new(SqlExpression::FunctionCall {
64 name: "UPPER".to_string(),
65 args: vec![self.transform_expression(*right)],
66 distinct: false,
67 }),
68 }
69 }
70
71 SqlExpression::BinaryOp { left, op, right } => SqlExpression::BinaryOp {
73 left: Box::new(self.transform_expression(*left)),
74 op,
75 right: Box::new(self.transform_expression(*right)),
76 },
77
78 SqlExpression::FunctionCall {
79 name,
80 args,
81 distinct,
82 } => SqlExpression::FunctionCall {
83 name,
84 args: args
85 .into_iter()
86 .map(|arg| self.transform_expression(arg))
87 .collect(),
88 distinct,
89 },
90
91 SqlExpression::CaseExpression {
92 when_branches,
93 else_branch,
94 } => SqlExpression::CaseExpression {
95 when_branches: when_branches
96 .into_iter()
97 .map(|branch| WhenBranch {
98 condition: Box::new(self.transform_expression(*branch.condition)),
99 result: Box::new(self.transform_expression(*branch.result)),
100 })
101 .collect(),
102 else_branch: else_branch.map(|e| Box::new(self.transform_expression(*e))),
103 },
104
105 SqlExpression::SimpleCaseExpression {
106 expr,
107 when_branches,
108 else_branch,
109 } => SqlExpression::SimpleCaseExpression {
110 expr: Box::new(self.transform_expression(*expr)),
111 when_branches: when_branches
112 .into_iter()
113 .map(|branch| SimpleWhenBranch {
114 value: Box::new(self.transform_expression(*branch.value)),
115 result: Box::new(self.transform_expression(*branch.result)),
116 })
117 .collect(),
118 else_branch: else_branch.map(|e| Box::new(self.transform_expression(*e))),
119 },
120
121 SqlExpression::Between { expr, lower, upper } => SqlExpression::Between {
122 expr: Box::new(self.transform_expression(*expr)),
123 lower: Box::new(self.transform_expression(*lower)),
124 upper: Box::new(self.transform_expression(*upper)),
125 },
126
127 SqlExpression::InList { expr, values } => SqlExpression::InList {
128 expr: Box::new(self.transform_expression(*expr)),
129 values: values
130 .into_iter()
131 .map(|v| self.transform_expression(v))
132 .collect(),
133 },
134
135 SqlExpression::InSubquery { expr, subquery } => SqlExpression::InSubquery {
136 expr: Box::new(self.transform_expression(*expr)),
137 subquery: Box::new(self.transform_statement(*subquery)),
138 },
139
140 SqlExpression::NotInList { expr, values } => SqlExpression::NotInList {
141 expr: Box::new(self.transform_expression(*expr)),
142 values: values
143 .into_iter()
144 .map(|v| self.transform_expression(v))
145 .collect(),
146 },
147
148 SqlExpression::MethodCall {
149 object,
150 method,
151 args,
152 } => SqlExpression::MethodCall {
153 object,
154 method,
155 args: args
156 .into_iter()
157 .map(|arg| self.transform_expression(arg))
158 .collect(),
159 },
160
161 SqlExpression::ChainedMethodCall { base, method, args } => {
162 SqlExpression::ChainedMethodCall {
163 base: Box::new(self.transform_expression(*base)),
164 method,
165 args: args
166 .into_iter()
167 .map(|arg| self.transform_expression(arg))
168 .collect(),
169 }
170 }
171
172 SqlExpression::Not { expr } => SqlExpression::Not {
173 expr: Box::new(self.transform_expression(*expr)),
174 },
175
176 SqlExpression::ScalarSubquery { query } => SqlExpression::ScalarSubquery {
177 query: Box::new(self.transform_statement(*query)),
178 },
179
180 SqlExpression::NotInSubquery { expr, subquery } => SqlExpression::NotInSubquery {
181 expr: Box::new(self.transform_expression(*expr)),
182 subquery: Box::new(self.transform_statement(*subquery)),
183 },
184
185 SqlExpression::WindowFunction {
186 name,
187 args,
188 window_spec,
189 } => SqlExpression::WindowFunction {
190 name,
191 args: args
192 .into_iter()
193 .map(|arg| self.transform_expression(arg))
194 .collect(),
195 window_spec,
196 },
197
198 SqlExpression::Unnest { column, delimiter } => SqlExpression::Unnest {
199 column: Box::new(self.transform_expression(*column)),
200 delimiter,
201 },
202
203 _ => expr,
205 }
206 }
207
208 fn transform_where_clause(&self, where_clause: WhereClause) -> WhereClause {
210 WhereClause {
211 conditions: where_clause
212 .conditions
213 .into_iter()
214 .map(|condition| Condition {
215 expr: self.transform_expression(condition.expr),
216 connector: condition.connector,
217 })
218 .collect(),
219 }
220 }
221
222 fn transform_select_items(&self, items: Vec<SelectItem>) -> Vec<SelectItem> {
224 items
225 .into_iter()
226 .map(|item| match item {
227 SelectItem::Expression {
228 expr,
229 alias,
230 leading_comments,
231 trailing_comment,
232 } => SelectItem::Expression {
233 expr: self.transform_expression(expr),
234 alias,
235 leading_comments,
236 trailing_comment,
237 },
238 SelectItem::Column {
239 column,
240 leading_comments,
241 trailing_comment,
242 } => SelectItem::Column {
243 column,
244 leading_comments,
245 trailing_comment,
246 },
247 SelectItem::Star {
248 table_prefix,
249 leading_comments,
250 trailing_comment,
251 } => SelectItem::Star {
252 table_prefix,
253 leading_comments,
254 trailing_comment,
255 },
256 SelectItem::StarExclude {
257 table_prefix,
258 excluded_columns,
259 leading_comments,
260 trailing_comment,
261 } => SelectItem::StarExclude {
262 table_prefix,
263 excluded_columns,
264 leading_comments,
265 trailing_comment,
266 },
267 })
268 .collect()
269 }
270
271 fn transform_order_by(&self, items: Vec<OrderByItem>) -> Vec<OrderByItem> {
273 items
274 .into_iter()
275 .map(|item| OrderByItem {
276 expr: self.transform_expression(item.expr),
277 direction: item.direction,
278 })
279 .collect()
280 }
281
282 fn transform_group_by(&self, exprs: Vec<SqlExpression>) -> Vec<SqlExpression> {
284 exprs
285 .into_iter()
286 .map(|e| self.transform_expression(e))
287 .collect()
288 }
289
290 fn transform_ctes(&self, ctes: Vec<CTE>) -> Vec<CTE> {
292 ctes.into_iter()
293 .map(|cte| {
294 let cte_type = match cte.cte_type {
295 CTEType::Standard(stmt) => CTEType::Standard(self.transform_statement(stmt)),
296 CTEType::Web(web_spec) => CTEType::Web(web_spec), };
298 CTE {
299 name: cte.name,
300 column_list: cte.column_list,
301 cte_type,
302 }
303 })
304 .collect()
305 }
306
307 fn transform_statement(&self, mut stmt: SelectStatement) -> SelectStatement {
309 if !stmt.ctes.is_empty() {
311 stmt.ctes = self.transform_ctes(stmt.ctes);
312 }
313
314 stmt.select_items = self.transform_select_items(stmt.select_items);
316
317 if let Some(where_clause) = stmt.where_clause {
319 stmt.where_clause = Some(self.transform_where_clause(where_clause));
320 }
321
322 if let Some(having) = stmt.having {
324 stmt.having = Some(self.transform_expression(having));
325 }
326
327 if let Some(order_by) = stmt.order_by {
329 stmt.order_by = Some(self.transform_order_by(order_by));
330 }
331
332 if let Some(group_by) = stmt.group_by {
334 stmt.group_by = Some(self.transform_group_by(group_by));
335 }
336
337 if let Some(qualify) = stmt.qualify {
339 stmt.qualify = Some(self.transform_expression(qualify));
340 }
341
342 stmt
343 }
344}
345
346impl Default for ILikeToLikeTransformer {
347 fn default() -> Self {
348 Self::new()
349 }
350}
351
352impl ASTTransformer for ILikeToLikeTransformer {
353 fn name(&self) -> &str {
354 "ILikeToLikeTransformer"
355 }
356
357 fn description(&self) -> &str {
358 "Converts ILIKE (case-insensitive LIKE) to UPPER() LIKE UPPER() pattern"
359 }
360
361 fn transform(&mut self, stmt: SelectStatement) -> Result<SelectStatement> {
362 Ok(self.transform_statement(stmt))
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369 use crate::sql::parser::ast::{ColumnRef, QuoteStyle};
370
371 #[test]
372 fn test_ilike_simple() {
373 let expr = SqlExpression::BinaryOp {
374 left: Box::new(SqlExpression::Column(ColumnRef::unquoted(
375 "email".to_string(),
376 ))),
377 op: "ILIKE".to_string(),
378 right: Box::new(SqlExpression::StringLiteral("%@gmail.com".to_string())),
379 };
380
381 let transformer = ILikeToLikeTransformer::new();
382 let result = transformer.transform_expression(expr);
383
384 match result {
386 SqlExpression::BinaryOp { left, op, right } => {
387 assert_eq!(op, "LIKE");
388
389 match *left {
391 SqlExpression::FunctionCall { ref name, .. } => {
392 assert_eq!(name, "UPPER");
393 }
394 _ => panic!("Expected FunctionCall on left"),
395 }
396
397 match *right {
399 SqlExpression::FunctionCall { ref name, .. } => {
400 assert_eq!(name, "UPPER");
401 }
402 _ => panic!("Expected FunctionCall on right"),
403 }
404 }
405 _ => panic!("Expected BinaryOp"),
406 }
407 }
408
409 #[test]
410 fn test_ilike_in_where_clause() {
411 let mut stmt = SelectStatement::default();
412
413 stmt.where_clause = Some(WhereClause {
414 conditions: vec![Condition {
415 expr: SqlExpression::BinaryOp {
416 left: Box::new(SqlExpression::Column(ColumnRef::unquoted(
417 "name".to_string(),
418 ))),
419 op: "ILIKE".to_string(),
420 right: Box::new(SqlExpression::StringLiteral("%john%".to_string())),
421 },
422 connector: None,
423 }],
424 });
425
426 let mut transformer = ILikeToLikeTransformer::new();
427 let result = transformer.transform(stmt).unwrap();
428
429 let where_clause = result.where_clause.unwrap();
430 let condition = &where_clause.conditions[0];
431
432 match &condition.expr {
433 SqlExpression::BinaryOp { op, .. } => {
434 assert_eq!(op, "LIKE");
435 }
436 _ => panic!("Expected BinaryOp"),
437 }
438 }
439
440 #[test]
441 fn test_like_unchanged() {
442 let expr = SqlExpression::BinaryOp {
443 left: Box::new(SqlExpression::Column(ColumnRef::unquoted(
444 "email".to_string(),
445 ))),
446 op: "LIKE".to_string(),
447 right: Box::new(SqlExpression::StringLiteral("%@gmail.com".to_string())),
448 };
449
450 let transformer = ILikeToLikeTransformer::new();
451 let result = transformer.transform_expression(expr.clone());
452
453 match result {
455 SqlExpression::BinaryOp { op, .. } => {
456 assert_eq!(op, "LIKE");
457 }
459 _ => panic!("Expected BinaryOp"),
460 }
461 }
462}