1use crate::query_plan::pipeline::ASTTransformer;
36use crate::sql::parser::ast::{SelectItem, SelectStatement, SqlExpression};
37use crate::sql::parser::walk;
38use anyhow::Result;
39use std::collections::HashMap;
40use tracing::debug;
41
42pub struct WhereAliasExpander {
44 expansions: usize,
46}
47
48impl WhereAliasExpander {
49 pub fn new() -> Self {
50 Self { expansions: 0 }
51 }
52
53 fn extract_aliases(select_items: &[SelectItem]) -> HashMap<String, SqlExpression> {
56 let mut aliases = HashMap::new();
57
58 for item in select_items {
59 if let SelectItem::Expression { expr, alias, .. } = item {
60 if !alias.is_empty() {
61 aliases.insert(alias.clone(), expr.clone());
62 debug!("Found SELECT alias: {} -> {:?}", alias, expr);
63 }
64 }
65 }
66
67 aliases
68 }
69
70 fn expand_expression(
83 expr: &SqlExpression,
84 aliases: &HashMap<String, SqlExpression>,
85 ) -> (SqlExpression, bool) {
86 match expr {
87 SqlExpression::Column(col_ref) => {
90 if col_ref.table_prefix.is_none() {
91 if let Some(alias_expr) = aliases.get(&col_ref.name) {
92 debug!(
93 "Expanding alias '{}' in WHERE to: {:?}",
94 col_ref.name, alias_expr
95 );
96 return (alias_expr.clone(), true);
97 }
98 }
99 (expr.clone(), false)
100 }
101
102 SqlExpression::MethodCall {
107 object,
108 method,
109 args,
110 } => {
111 let mut expanded = false;
112 let new_args: Vec<SqlExpression> = args
113 .iter()
114 .map(|arg| {
115 let (new_arg, arg_expanded) = Self::expand_expression(arg, aliases);
116 expanded = expanded || arg_expanded;
117 new_arg
118 })
119 .collect();
120
121 let mut new_object = object.clone();
122 if let Some(SqlExpression::Column(col_ref)) = aliases.get(object) {
123 if col_ref.table_prefix.is_none() {
124 debug!(
125 "Expanding alias '{}' in WHERE method call to column '{}'",
126 object, col_ref.name
127 );
128 new_object = col_ref.name.clone();
129 expanded = true;
130 }
131 }
132
133 (
134 SqlExpression::MethodCall {
135 object: new_object,
136 method: method.clone(),
137 args: new_args,
138 },
139 expanded,
140 )
141 }
142
143 other => {
147 let mut expanded = false;
148 let new_expr = walk::map_children(other.clone(), |child| {
149 let (new_child, child_expanded) = Self::expand_expression(&child, aliases);
150 expanded = expanded || child_expanded;
151 new_child
152 });
153 (new_expr, expanded)
154 }
155 }
156 }
157
158 fn expand_where_clause(
160 &mut self,
161 where_clause: &mut crate::sql::parser::ast::WhereClause,
162 aliases: &HashMap<String, SqlExpression>,
163 ) -> bool {
164 let mut any_expanded = false;
165
166 for condition in &mut where_clause.conditions {
167 let (new_expr, expanded) = Self::expand_expression(&condition.expr, aliases);
168 if expanded {
169 condition.expr = new_expr;
170 any_expanded = true;
171 self.expansions += 1;
172 }
173 }
174
175 any_expanded
176 }
177}
178
179impl Default for WhereAliasExpander {
180 fn default() -> Self {
181 Self::new()
182 }
183}
184
185impl ASTTransformer for WhereAliasExpander {
186 fn name(&self) -> &str {
187 "WhereAliasExpander"
188 }
189
190 fn description(&self) -> &str {
191 "Expands SELECT aliases in WHERE clauses to their full expressions"
192 }
193
194 fn transform(&mut self, mut stmt: SelectStatement) -> Result<SelectStatement> {
195 if stmt.where_clause.is_none() {
197 return Ok(stmt);
198 }
199
200 let aliases = Self::extract_aliases(&stmt.select_items);
202
203 if aliases.is_empty() {
204 return Ok(stmt);
206 }
207
208 if let Some(ref mut where_clause) = stmt.where_clause {
210 let expanded = self.expand_where_clause(where_clause, &aliases);
211 if expanded {
212 debug!(
213 "Expanded {} alias reference(s) in WHERE clause",
214 self.expansions
215 );
216 }
217 }
218
219 Ok(stmt)
220 }
221
222 fn begin(&mut self) -> Result<()> {
223 self.expansions = 0;
225 Ok(())
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232 use crate::sql::parser::ast::{ColumnRef, Condition, QuoteStyle, WhereClause};
233
234 #[test]
235 fn test_extract_aliases() {
236 let double_a_expr = SqlExpression::BinaryOp {
237 left: Box::new(SqlExpression::Column(ColumnRef {
238 name: "a".to_string(),
239 quote_style: QuoteStyle::None,
240 table_prefix: None,
241 })),
242 op: "*".to_string(),
243 right: Box::new(SqlExpression::NumberLiteral("2".to_string())),
244 };
245
246 let select_items = vec![SelectItem::Expression {
247 expr: double_a_expr.clone(),
248 alias: "double_a".to_string(),
249 leading_comments: vec![],
250 trailing_comment: None,
251 }];
252
253 let aliases = WhereAliasExpander::extract_aliases(&select_items);
254 assert_eq!(aliases.len(), 1);
255 assert!(aliases.contains_key("double_a"));
256 }
257
258 #[test]
259 fn test_expand_simple_column_reference() {
260 let aliases = HashMap::from([(
261 "double_a".to_string(),
262 SqlExpression::BinaryOp {
263 left: Box::new(SqlExpression::Column(ColumnRef::unquoted("a".to_string()))),
264 op: "*".to_string(),
265 right: Box::new(SqlExpression::NumberLiteral("2".to_string())),
266 },
267 )]);
268
269 let expr = SqlExpression::Column(ColumnRef::unquoted("double_a".to_string()));
270 let (expanded, changed) = WhereAliasExpander::expand_expression(&expr, &aliases);
271
272 assert!(changed);
273 assert!(matches!(expanded, SqlExpression::BinaryOp { .. }));
274 }
275
276 #[test]
277 fn test_expand_in_binary_op() {
278 let aliases = HashMap::from([(
279 "double_a".to_string(),
280 SqlExpression::BinaryOp {
281 left: Box::new(SqlExpression::Column(ColumnRef::unquoted("a".to_string()))),
282 op: "*".to_string(),
283 right: Box::new(SqlExpression::NumberLiteral("2".to_string())),
284 },
285 )]);
286
287 let expr = SqlExpression::BinaryOp {
288 left: Box::new(SqlExpression::Column(ColumnRef::unquoted(
289 "double_a".to_string(),
290 ))),
291 op: ">".to_string(),
292 right: Box::new(SqlExpression::NumberLiteral("10".to_string())),
293 };
294
295 let (expanded, changed) = WhereAliasExpander::expand_expression(&expr, &aliases);
296
297 assert!(changed);
298 if let SqlExpression::BinaryOp { left, op, right } = expanded {
299 assert_eq!(op, ">");
300 assert!(matches!(left.as_ref(), SqlExpression::BinaryOp { .. }));
301 assert!(matches!(
302 right.as_ref(),
303 SqlExpression::NumberLiteral(s) if s == "10"
304 ));
305 } else {
306 panic!("Expected BinaryOp");
307 }
308 }
309
310 #[test]
311 fn test_transform_with_no_where() {
312 let mut transformer = WhereAliasExpander::new();
313 let stmt = SelectStatement {
314 where_clause: None,
315 ..Default::default()
316 };
317
318 let result = transformer.transform(stmt);
319 assert!(result.is_ok());
320 }
321
322 #[test]
323 fn test_transform_expands_alias() {
324 let mut transformer = WhereAliasExpander::new();
325
326 let double_a_expr = SqlExpression::BinaryOp {
327 left: Box::new(SqlExpression::Column(ColumnRef::unquoted("a".to_string()))),
328 op: "*".to_string(),
329 right: Box::new(SqlExpression::NumberLiteral("2".to_string())),
330 };
331
332 let stmt = SelectStatement {
333 select_items: vec![SelectItem::Expression {
334 expr: double_a_expr.clone(),
335 alias: "double_a".to_string(),
336 leading_comments: vec![],
337 trailing_comment: None,
338 }],
339 where_clause: Some(WhereClause {
340 conditions: vec![Condition {
341 expr: SqlExpression::BinaryOp {
342 left: Box::new(SqlExpression::Column(ColumnRef::unquoted(
343 "double_a".to_string(),
344 ))),
345 op: ">".to_string(),
346 right: Box::new(SqlExpression::NumberLiteral("10".to_string())),
347 },
348 connector: None,
349 }],
350 }),
351 ..Default::default()
352 };
353
354 let result = transformer.transform(stmt).unwrap();
355
356 if let Some(where_clause) = &result.where_clause {
358 if let SqlExpression::BinaryOp { left, .. } = &where_clause.conditions[0].expr {
359 assert!(matches!(left.as_ref(), SqlExpression::BinaryOp { .. }));
361 } else {
362 panic!("Expected BinaryOp in WHERE");
363 }
364 } else {
365 panic!("Expected WHERE clause");
366 }
367
368 assert_eq!(transformer.expansions, 1);
369 }
370
371 #[test]
372 fn test_expand_alias_in_method_call_receiver() {
373 let aliases = HashMap::from([(
377 "name".to_string(),
378 SqlExpression::Column(ColumnRef {
379 name: "name.common".to_string(),
380 quote_style: QuoteStyle::DoubleQuotes,
381 table_prefix: None,
382 }),
383 )]);
384
385 let expr = SqlExpression::MethodCall {
386 object: "name".to_string(),
387 method: "Contains".to_string(),
388 args: vec![SqlExpression::StringLiteral("united".to_string())],
389 };
390
391 let (expanded, changed) = WhereAliasExpander::expand_expression(&expr, &aliases);
392
393 assert!(changed);
394 match expanded {
395 SqlExpression::MethodCall { object, method, .. } => {
396 assert_eq!(object, "name.common");
397 assert_eq!(method, "Contains");
398 }
399 other => panic!("Expected MethodCall, got {other:?}"),
400 }
401 }
402
403 #[test]
404 fn test_does_not_expand_method_call_for_nonalias() {
405 let aliases = HashMap::from([(
407 "name".to_string(),
408 SqlExpression::Column(ColumnRef::unquoted("name.common".to_string())),
409 )]);
410
411 let expr = SqlExpression::MethodCall {
412 object: "capital".to_string(),
413 method: "Contains".to_string(),
414 args: vec![SqlExpression::StringLiteral("x".to_string())],
415 };
416
417 let (expanded, changed) = WhereAliasExpander::expand_expression(&expr, &aliases);
418
419 assert!(!changed);
420 assert!(matches!(
421 expanded,
422 SqlExpression::MethodCall { object, .. } if object == "capital"
423 ));
424 }
425
426 #[test]
427 fn test_expands_alias_on_in_subquery_lhs_not_body() {
428 let double = SqlExpression::BinaryOp {
432 left: Box::new(SqlExpression::Column(ColumnRef::unquoted("price".into()))),
433 op: "*".to_string(),
434 right: Box::new(SqlExpression::NumberLiteral("2".to_string())),
435 };
436 let aliases = HashMap::from([("dbl".to_string(), double.clone())]);
437
438 let body = SelectStatement {
441 where_clause: Some(WhereClause {
442 conditions: vec![Condition {
443 expr: SqlExpression::Column(ColumnRef::unquoted("dbl".into())),
444 connector: None,
445 }],
446 }),
447 ..Default::default()
448 };
449
450 let expr = SqlExpression::InSubquery {
451 expr: Box::new(SqlExpression::Column(ColumnRef::unquoted("dbl".into()))),
452 subquery: Box::new(body.clone()),
453 };
454
455 let (expanded, changed) = WhereAliasExpander::expand_expression(&expr, &aliases);
456 assert!(changed, "the LHS alias should have been expanded");
457
458 match expanded {
459 SqlExpression::InSubquery { expr, subquery } => {
460 assert!(matches!(expr.as_ref(), SqlExpression::BinaryOp { .. }));
462 let inner = &subquery.where_clause.as_ref().unwrap().conditions[0].expr;
464 assert!(
465 matches!(inner, SqlExpression::Column(c) if c.name == "dbl"),
466 "subquery body must not be touched (different scope), got {inner:?}"
467 );
468 }
469 other => panic!("expected InSubquery, got {other:?}"),
470 }
471 }
472
473 #[test]
474 fn test_does_not_expand_table_prefixed_columns() {
475 let aliases = HashMap::from([(
476 "double_a".to_string(),
477 SqlExpression::BinaryOp {
478 left: Box::new(SqlExpression::Column(ColumnRef::unquoted("a".to_string()))),
479 op: "*".to_string(),
480 right: Box::new(SqlExpression::NumberLiteral("2".to_string())),
481 },
482 )]);
483
484 let expr = SqlExpression::Column(ColumnRef {
486 name: "double_a".to_string(),
487 quote_style: QuoteStyle::None,
488 table_prefix: Some("t".to_string()),
489 });
490
491 let (expanded, changed) = WhereAliasExpander::expand_expression(&expr, &aliases);
492
493 assert!(!changed);
494 assert!(matches!(expanded, SqlExpression::Column(_)));
495 }
496}