1use std::collections::{HashMap, HashSet};
11
12use crate::builder::engine;
13use crate::expressions::*;
14use crate::traversal::ExpressionWalk;
15
16fn xform<F: Fn(Expression) -> Expression>(expr: Expression, fun: F) -> Expression {
19 crate::traversal::transform(expr, &|node| Ok(Some(fun(node))))
20 .unwrap_or_else(|_| Expression::Null(Null))
21}
22
23pub fn add_select_columns(expr: Expression, columns: Vec<Expression>) -> Expression {
32 let mut expression = expr;
33 let _ = engine::append_select(&mut expression, columns, true);
34 expression
35}
36
37pub fn remove_select_columns<F: Fn(&Expression) -> bool>(
39 expr: Expression,
40 predicate: F,
41) -> Expression {
42 if let Expression::Select(mut sel) = expr {
43 sel.expressions.retain(|e| !predicate(e));
44 Expression::Select(sel)
45 } else {
46 expr
47 }
48}
49
50pub fn set_distinct(expr: Expression, distinct: bool) -> Expression {
52 let mut expression = expr;
53 let _ = engine::apply_distinct(&mut expression, distinct);
54 expression
55}
56
57pub fn add_where(expr: Expression, condition: Expression, use_or: bool) -> Expression {
67 if !use_or {
68 if !matches!(expr, Expression::Select(_)) {
69 return expr;
70 }
71 let mut expression = expr;
72 let _ = engine::apply_where(&mut expression, condition, true);
73 return expression;
74 }
75 if let Expression::Select(mut sel) = expr {
76 sel.where_clause = Some(match sel.where_clause.take() {
77 Some(existing) => {
78 let combined = if use_or {
79 Expression::Or(Box::new(BinaryOp::new(existing.this, condition)))
80 } else {
81 Expression::And(Box::new(BinaryOp::new(existing.this, condition)))
82 };
83 Where { this: combined }
84 }
85 None => Where { this: condition },
86 });
87 Expression::Select(sel)
88 } else {
89 expr
90 }
91}
92
93pub fn remove_where(expr: Expression) -> Expression {
95 if let Expression::Select(mut sel) = expr {
96 sel.where_clause = None;
97 Expression::Select(sel)
98 } else {
99 expr
100 }
101}
102
103pub fn set_limit(expr: Expression, limit: usize) -> Expression {
109 set_limit_expr(expr, Expression::number(limit as i64))
110}
111
112pub fn set_limit_expr(expr: Expression, limit: Expression) -> Expression {
114 let mut expression = expr;
115 let _ = engine::apply_limit(&mut expression, limit);
116 expression
117}
118
119pub fn set_offset(expr: Expression, offset: usize) -> Expression {
121 set_offset_expr(expr, Expression::number(offset as i64))
122}
123
124pub fn set_offset_expr(expr: Expression, offset: Expression) -> Expression {
126 let mut expression = expr;
127 let _ = engine::apply_offset(&mut expression, offset);
128 expression
129}
130
131pub fn set_order_by(expr: Expression, expressions: Vec<Expression>) -> Expression {
136 let mut expression = expr;
137 let values = expressions.into_iter().map(engine::ordered).collect();
138 let _ = engine::apply_order_by(&mut expression, values, false);
139 expression
140}
141
142pub fn remove_limit_offset(expr: Expression) -> Expression {
144 if let Expression::Select(mut sel) = expr {
145 sel.limit = None;
146 sel.offset = None;
147 Expression::Select(sel)
148 } else {
149 expr
150 }
151}
152
153pub fn rename_columns(expr: Expression, mapping: &HashMap<String, String>) -> Expression {
162 xform(expr, |node| match node {
163 Expression::Column(mut col) => {
164 if let Some(new_name) = mapping.get(&col.name.name) {
165 col.name.name = new_name.clone();
166 }
167 Expression::Column(col)
168 }
169 other => other,
170 })
171}
172
173#[derive(Debug, Clone)]
175pub struct RenameTablesOptions {
176 pub alias_renamed_tables: bool,
178 pub preserve_existing_aliases: bool,
180}
181
182impl Default for RenameTablesOptions {
183 fn default() -> Self {
184 Self {
185 alias_renamed_tables: false,
186 preserve_existing_aliases: true,
187 }
188 }
189}
190
191impl RenameTablesOptions {
192 pub fn new() -> Self {
193 Self::default()
194 }
195
196 pub fn with_alias_renamed_tables(mut self, alias: bool) -> Self {
197 self.alias_renamed_tables = alias;
198 self
199 }
200
201 pub fn with_preserve_existing_aliases(mut self, preserve: bool) -> Self {
202 self.preserve_existing_aliases = preserve;
203 self
204 }
205}
206
207pub fn rename_tables(expr: Expression, mapping: &HashMap<String, String>) -> Expression {
209 rename_tables_with_options(expr, mapping, &RenameTablesOptions::default())
210}
211
212pub fn rename_tables_with_options(
214 expr: Expression,
215 mapping: &HashMap<String, String>,
216 options: &RenameTablesOptions,
217) -> Expression {
218 xform(expr, |node| match node {
219 Expression::Table(mut tbl) => {
220 if let Some(new_name) = mapping.get(&tbl.name.name) {
221 tbl.name.name = new_name.clone();
222 if options.alias_renamed_tables
223 && (!options.preserve_existing_aliases || tbl.alias.is_none())
224 {
225 tbl.alias = Some(Identifier::new(new_name));
226 tbl.alias_explicit_as = true;
227 }
228 }
229 Expression::Table(tbl)
230 }
231 Expression::Column(mut col) => {
232 if let Some(ref mut table_id) = col.table {
233 if let Some(new_name) = mapping.get(&table_id.name) {
234 table_id.name = new_name.clone();
235 }
236 }
237 Expression::Column(col)
238 }
239 other => other,
240 })
241}
242
243pub fn qualify_columns(expr: Expression, table_name: &str) -> Expression {
247 let table = table_name.to_string();
248 xform(expr, move |node| match node {
249 Expression::Column(mut col) => {
250 if col.table.is_none() {
251 col.table = Some(Identifier::new(&table));
252 }
253 Expression::Column(col)
254 }
255 other => other,
256 })
257}
258
259pub fn replace_nodes<F: Fn(&Expression) -> bool>(
265 expr: Expression,
266 predicate: F,
267 replacement: Expression,
268) -> Expression {
269 xform(expr, |node| {
270 if predicate(&node) {
271 replacement.clone()
272 } else {
273 node
274 }
275 })
276}
277
278pub fn replace_by_type<F, R>(expr: Expression, predicate: F, replacer: R) -> Expression
280where
281 F: Fn(&Expression) -> bool,
282 R: Fn(Expression) -> Expression,
283{
284 xform(expr, |node| {
285 if predicate(&node) {
286 replacer(node)
287 } else {
288 node
289 }
290 })
291}
292
293pub fn remove_nodes<F: Fn(&Expression) -> bool>(expr: Expression, predicate: F) -> Expression {
299 xform(expr, |node| {
300 if predicate(&node) {
301 Expression::Null(Null)
302 } else {
303 node
304 }
305 })
306}
307
308pub fn get_column_names(expr: &Expression) -> Vec<String> {
314 expr.find_all(|e| matches!(e, Expression::Column(_)))
315 .into_iter()
316 .filter_map(|e| {
317 if let Expression::Column(col) = e {
318 Some(col.name.name.clone())
319 } else {
320 None
321 }
322 })
323 .collect()
324}
325
326pub fn get_output_column_names(expr: &Expression) -> Vec<String> {
336 output_column_names_from_query(expr)
337}
338
339fn output_column_names_from_query(expr: &Expression) -> Vec<String> {
340 match expr {
341 Expression::Select(select) => select_output_column_names(select),
342 Expression::Union(union) => output_column_names_from_query(&union.left),
343 Expression::Intersect(intersect) => output_column_names_from_query(&intersect.left),
344 Expression::Except(except) => output_column_names_from_query(&except.left),
345 Expression::Subquery(subquery) => output_column_names_from_query(&subquery.this),
346 _ => Vec::new(),
347 }
348}
349
350fn select_output_column_names(select: &Select) -> Vec<String> {
351 let mut names = Vec::new();
352 for expr in &select.expressions {
353 if let Some(name) = expression_output_name(expr) {
354 names.push(name);
355 }
356 }
357 names
358}
359
360fn expression_output_name(expr: &Expression) -> Option<String> {
361 match expr {
362 Expression::Alias(alias) => Some(alias.alias.name.clone()),
363 Expression::Column(col) => Some(col.name.name.clone()),
364 Expression::Star(_) => Some("*".to_string()),
365 Expression::Identifier(id) => Some(id.name.clone()),
366 Expression::Aliases(aliases) => aliases.expressions.iter().find_map(|e| match e {
367 Expression::Identifier(id) => Some(id.name.clone()),
368 _ => None,
369 }),
370 _ => None,
371 }
372}
373
374pub fn get_table_names(expr: &Expression) -> Vec<String> {
376 fn collect_cte_aliases(with_clause: &With, aliases: &mut HashSet<String>) {
377 for cte in &with_clause.ctes {
378 aliases.insert(cte.alias.name.clone());
379 }
380 }
381
382 fn push_table_ref_name(
383 table: &TableRef,
384 cte_aliases: &HashSet<String>,
385 names: &mut Vec<String>,
386 ) {
387 let name = table.name.name.clone();
388 if !name.is_empty() && !cte_aliases.contains(&name) {
389 names.push(name);
390 }
391 }
392
393 let mut cte_aliases: HashSet<String> = HashSet::new();
394 for node in expr.dfs() {
395 match node {
396 Expression::Select(select) => {
397 if let Some(with) = &select.with {
398 collect_cte_aliases(with, &mut cte_aliases);
399 }
400 }
401 Expression::Insert(insert) => {
402 if let Some(with) = &insert.with {
403 collect_cte_aliases(with, &mut cte_aliases);
404 }
405 }
406 Expression::Update(update) => {
407 if let Some(with) = &update.with {
408 collect_cte_aliases(with, &mut cte_aliases);
409 }
410 }
411 Expression::Delete(delete) => {
412 if let Some(with) = &delete.with {
413 collect_cte_aliases(with, &mut cte_aliases);
414 }
415 }
416 Expression::Union(union) => {
417 if let Some(with) = &union.with {
418 collect_cte_aliases(with, &mut cte_aliases);
419 }
420 }
421 Expression::Intersect(intersect) => {
422 if let Some(with) = &intersect.with {
423 collect_cte_aliases(with, &mut cte_aliases);
424 }
425 }
426 Expression::Except(except) => {
427 if let Some(with) = &except.with {
428 collect_cte_aliases(with, &mut cte_aliases);
429 }
430 }
431 Expression::CreateTable(create) => {
432 if let Some(with) = &create.with_cte {
433 collect_cte_aliases(with, &mut cte_aliases);
434 }
435 }
436 Expression::Merge(merge) => {
437 if let Some(with_) = &merge.with_ {
438 if let Expression::With(with_clause) = with_.as_ref() {
439 collect_cte_aliases(with_clause, &mut cte_aliases);
440 }
441 }
442 }
443 _ => {}
444 }
445 }
446
447 let mut names = Vec::new();
448 for node in expr.dfs() {
449 match node {
450 Expression::Table(tbl) => {
451 let name = tbl.name.name.clone();
452 if !name.is_empty() && !cte_aliases.contains(&name) {
453 names.push(name);
454 }
455 }
456 Expression::Insert(insert) => {
457 push_table_ref_name(&insert.table, &cte_aliases, &mut names);
458 }
459 Expression::Update(update) => {
460 push_table_ref_name(&update.table, &cte_aliases, &mut names);
461 for table in &update.extra_tables {
462 push_table_ref_name(table, &cte_aliases, &mut names);
463 }
464 }
465 Expression::Delete(delete) => {
466 push_table_ref_name(&delete.table, &cte_aliases, &mut names);
467 for table in &delete.using {
468 push_table_ref_name(table, &cte_aliases, &mut names);
469 }
470 for table in &delete.tables {
471 push_table_ref_name(table, &cte_aliases, &mut names);
472 }
473 }
474 Expression::CreateTable(create) => {
475 push_table_ref_name(&create.name, &cte_aliases, &mut names);
476 if let Some(as_select) = &create.as_select {
477 names.extend(get_table_names(as_select));
478 }
479 if let Some(with) = &create.with_cte {
480 for cte in &with.ctes {
481 names.extend(get_table_names(&cte.this));
482 }
483 }
484 }
485 Expression::Cache(cache) => {
486 let name = cache.table.name.clone();
487 if !name.is_empty() && !cte_aliases.contains(&name) {
488 names.push(name);
489 }
490 }
491 Expression::Uncache(uncache) => {
492 let name = uncache.table.name.clone();
493 if !name.is_empty() && !cte_aliases.contains(&name) {
494 names.push(name);
495 }
496 }
497 Expression::CreateSynonym(synonym) => {
498 push_table_ref_name(&synonym.name, &cte_aliases, &mut names);
499 push_table_ref_name(&synonym.target, &cte_aliases, &mut names);
500 }
501 _ => {}
502 }
503 }
504
505 names
506}
507
508pub fn get_identifiers(expr: &Expression) -> Vec<&Expression> {
510 expr.find_all(|e| matches!(e, Expression::Identifier(_)))
511}
512
513pub fn get_functions(expr: &Expression) -> Vec<&Expression> {
515 expr.find_all(|e| {
516 matches!(
517 e,
518 Expression::Function(_) | Expression::AggregateFunction(_)
519 )
520 })
521}
522
523pub fn get_literals(expr: &Expression) -> Vec<&Expression> {
525 expr.find_all(|e| {
526 matches!(
527 e,
528 Expression::Literal(_) | Expression::Boolean(_) | Expression::Null(_)
529 )
530 })
531}
532
533pub fn get_subqueries(expr: &Expression) -> Vec<&Expression> {
535 expr.find_all(|e| matches!(e, Expression::Subquery(_)))
536}
537
538pub fn get_aggregate_functions(expr: &Expression) -> Vec<&Expression> {
543 expr.find_all(|e| {
544 matches!(
545 e,
546 Expression::AggregateFunction(_)
547 | Expression::Count(_)
548 | Expression::Sum(_)
549 | Expression::Avg(_)
550 | Expression::Min(_)
551 | Expression::Max(_)
552 | Expression::ApproxDistinct(_)
553 | Expression::ArrayAgg(_)
554 | Expression::GroupConcat(_)
555 | Expression::StringAgg(_)
556 | Expression::ListAgg(_)
557 )
558 })
559}
560
561pub fn get_window_functions(expr: &Expression) -> Vec<&Expression> {
563 expr.find_all(|e| matches!(e, Expression::WindowFunction(_)))
564}
565
566pub fn node_count(expr: &Expression) -> usize {
568 expr.dfs().count()
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574 use crate::parser::Parser;
575
576 fn parse_one(sql: &str) -> Expression {
577 let mut exprs = Parser::parse_sql(sql).unwrap();
578 exprs.remove(0)
579 }
580
581 #[test]
582 fn test_add_where() {
583 let expr = parse_one("SELECT a FROM t");
584 let cond = Expression::Eq(Box::new(BinaryOp::new(
585 Expression::column("b"),
586 Expression::number(1),
587 )));
588 let result = add_where(expr, cond, false);
589 let sql = result.sql();
590 assert!(sql.contains("WHERE"), "Expected WHERE in: {}", sql);
591 assert!(sql.contains("b = 1"), "Expected condition in: {}", sql);
592 }
593
594 #[test]
595 fn test_add_where_combines_with_and() {
596 let expr = parse_one("SELECT a FROM t WHERE x = 1");
597 let cond = Expression::Eq(Box::new(BinaryOp::new(
598 Expression::column("y"),
599 Expression::number(2),
600 )));
601 let result = add_where(expr, cond, false);
602 let sql = result.sql();
603 assert!(sql.contains("AND"), "Expected AND in: {}", sql);
604 }
605
606 #[test]
607 fn test_remove_where() {
608 let expr = parse_one("SELECT a FROM t WHERE x = 1");
609 let result = remove_where(expr);
610 let sql = result.sql();
611 assert!(!sql.contains("WHERE"), "Should not contain WHERE: {}", sql);
612 }
613
614 #[test]
615 fn test_set_limit() {
616 let expr = parse_one("SELECT a FROM t");
617 let result = set_limit(expr, 10);
618 let sql = result.sql();
619 assert!(sql.contains("LIMIT 10"), "Expected LIMIT in: {}", sql);
620 }
621
622 #[test]
623 fn test_set_limit_on_set_operation() {
624 let expr = parse_one("SELECT a FROM t UNION ALL SELECT a FROM u");
625 let result = set_limit(expr, 10);
626 let sql = result.sql();
627 assert_eq!(sql, "SELECT a FROM t UNION ALL SELECT a FROM u LIMIT 10");
628 }
629
630 #[test]
631 fn test_set_offset() {
632 let expr = parse_one("SELECT a FROM t");
633 let result = set_offset(expr, 5);
634 let sql = result.sql();
635 assert!(sql.contains("OFFSET 5"), "Expected OFFSET in: {}", sql);
636 }
637
638 #[test]
639 fn test_set_offset_on_set_operation() {
640 let expr = parse_one("SELECT a FROM t UNION ALL SELECT a FROM u");
641 let result = set_offset(expr, 5);
642 let sql = result.sql();
643 assert_eq!(sql, "SELECT a FROM t UNION ALL SELECT a FROM u OFFSET 5");
644 }
645
646 #[test]
647 fn test_set_order_by_on_set_operation() {
648 let expr = parse_one("SELECT a FROM t UNION ALL SELECT a FROM u");
649 let result = set_order_by(expr, vec![Expression::column("a")]);
650 let sql = result.sql();
651 assert_eq!(sql, "SELECT a FROM t UNION ALL SELECT a FROM u ORDER BY a");
652 }
653
654 #[test]
655 fn test_remove_limit_offset() {
656 let expr = parse_one("SELECT a FROM t LIMIT 10 OFFSET 5");
657 let result = remove_limit_offset(expr);
658 let sql = result.sql();
659 assert!(!sql.contains("LIMIT"), "Should not contain LIMIT: {}", sql);
660 assert!(
661 !sql.contains("OFFSET"),
662 "Should not contain OFFSET: {}",
663 sql
664 );
665 }
666
667 #[test]
668 fn test_get_column_names() {
669 let expr = parse_one("SELECT a, b, c FROM t");
670 let names = get_column_names(&expr);
671 assert!(names.contains(&"a".to_string()));
672 assert!(names.contains(&"b".to_string()));
673 assert!(names.contains(&"c".to_string()));
674 }
675
676 #[test]
677 fn test_get_output_column_names_select() {
678 let expr = parse_one("SELECT a, b AS c, 1 FROM t");
679 let names = get_output_column_names(&expr);
680 assert_eq!(names, vec!["a".to_string(), "c".to_string()]);
681 }
682
683 #[test]
684 fn test_get_output_column_names_union_left_projection() {
685 let expr =
686 parse_one("SELECT id, name FROM customers UNION ALL SELECT id, name FROM employees");
687 let names = get_output_column_names(&expr);
688 assert_eq!(names, vec!["id".to_string(), "name".to_string()]);
689 }
690
691 #[test]
692 fn test_get_output_column_names_union_uses_left_aliases() {
693 let expr = parse_one("SELECT id AS c1, name AS c2 FROM t1 UNION SELECT x, y FROM t2");
694 let names = get_output_column_names(&expr);
695 assert_eq!(names, vec!["c1".to_string(), "c2".to_string()]);
696 }
697
698 #[test]
699 fn test_get_column_names_union_still_returns_all_references() {
700 let expr =
701 parse_one("SELECT id, name FROM customers UNION ALL SELECT id, name FROM employees");
702 let names = get_column_names(&expr);
703 assert_eq!(
704 names,
705 vec![
706 "id".to_string(),
707 "name".to_string(),
708 "id".to_string(),
709 "name".to_string()
710 ]
711 );
712 }
713
714 #[test]
715 fn test_get_table_names() {
716 let expr = parse_one("SELECT a FROM users");
717 let names = get_table_names(&expr);
718 assert_eq!(names, vec!["users".to_string()]);
719 }
720
721 #[test]
722 fn test_get_table_names_excludes_cte_aliases() {
723 let expr = parse_one(
724 "WITH cte AS (SELECT * FROM users) SELECT * FROM cte JOIN orders o ON cte.id = o.id",
725 );
726 let names = get_table_names(&expr);
727 assert!(names.iter().any(|n| n == "users"));
728 assert!(names.iter().any(|n| n == "orders"));
729 assert!(!names.iter().any(|n| n == "cte"));
730 }
731
732 #[test]
733 fn test_get_table_names_includes_dml_targets() {
734 let insert_expr = parse_one("INSERT INTO users (id) VALUES (1)");
735 let insert_names = get_table_names(&insert_expr);
736 assert!(insert_names.iter().any(|n| n == "users"));
737
738 let update_expr =
739 parse_one("UPDATE users SET name = 'x' FROM accounts WHERE users.id = accounts.id");
740 let update_names = get_table_names(&update_expr);
741 assert!(update_names.iter().any(|n| n == "users"));
742 assert!(update_names.iter().any(|n| n == "accounts"));
743
744 let delete_expr =
745 parse_one("DELETE FROM users USING accounts WHERE users.id = accounts.id");
746 let delete_names = get_table_names(&delete_expr);
747 assert!(delete_names.iter().any(|n| n == "users"));
748 assert!(delete_names.iter().any(|n| n == "accounts"));
749
750 let create_expr = parse_one("CREATE TABLE out_table AS SELECT 1 AS id FROM src");
751 let create_names = get_table_names(&create_expr);
752 assert!(create_names.iter().any(|n| n == "out_table"));
753 assert!(create_names.iter().any(|n| n == "src"));
754 }
755
756 #[test]
757 fn test_node_count() {
758 let expr = parse_one("SELECT a FROM t");
759 let count = node_count(&expr);
760 assert!(count > 0, "Expected non-zero node count");
761 }
762
763 #[test]
764 fn test_rename_columns() {
765 let expr = parse_one("SELECT old_name FROM t");
766 let mut mapping = HashMap::new();
767 mapping.insert("old_name".to_string(), "new_name".to_string());
768 let result = rename_columns(expr, &mapping);
769 let sql = result.sql();
770 assert!(sql.contains("new_name"), "Expected new_name in: {}", sql);
771 assert!(
772 !sql.contains("old_name"),
773 "Should not contain old_name: {}",
774 sql
775 );
776 }
777
778 #[test]
779 fn test_rename_tables() {
780 let expr = parse_one("SELECT a FROM old_table");
781 let mut mapping = HashMap::new();
782 mapping.insert("old_table".to_string(), "new_table".to_string());
783 let result = rename_tables(expr, &mapping);
784 let sql = result.sql();
785 assert!(sql.contains("new_table"), "Expected new_table in: {}", sql);
786 }
787
788 #[test]
789 fn test_rename_tables_with_alias_renamed_tables() {
790 let expr = parse_one("SELECT a FROM old_table");
791 let mut mapping = HashMap::new();
792 mapping.insert("old_table".to_string(), "new_table".to_string());
793 let options = RenameTablesOptions::new().with_alias_renamed_tables(true);
794 let result = rename_tables_with_options(expr, &mapping, &options);
795 let sql = result.sql();
796
797 assert_eq!(sql, "SELECT a FROM new_table AS new_table");
798 }
799
800 #[test]
801 fn test_rename_tables_with_alias_preserves_existing_alias() {
802 let expr = parse_one("SELECT a FROM old_table AS t");
803 let mut mapping = HashMap::new();
804 mapping.insert("old_table".to_string(), "new_table".to_string());
805 let options = RenameTablesOptions::new().with_alias_renamed_tables(true);
806 let result = rename_tables_with_options(expr, &mapping, &options);
807 let sql = result.sql();
808
809 assert_eq!(sql, "SELECT a FROM new_table AS t");
810 }
811
812 #[test]
813 fn test_set_distinct() {
814 let expr = parse_one("SELECT a FROM t");
815 let result = set_distinct(expr, true);
816 let sql = result.sql();
817 assert!(sql.contains("DISTINCT"), "Expected DISTINCT in: {}", sql);
818 }
819
820 #[test]
821 fn test_add_select_columns() {
822 let expr = parse_one("SELECT a FROM t");
823 let result = add_select_columns(expr, vec![Expression::column("b")]);
824 let sql = result.sql();
825 assert!(
826 sql.contains("a, b") || sql.contains("a,b"),
827 "Expected a, b in: {}",
828 sql
829 );
830 }
831
832 #[test]
833 fn test_qualify_columns() {
834 let expr = parse_one("SELECT a, b FROM t");
835 let result = qualify_columns(expr, "t");
836 let sql = result.sql();
837 assert!(sql.contains("t.a"), "Expected t.a in: {}", sql);
838 assert!(sql.contains("t.b"), "Expected t.b in: {}", sql);
839 }
840
841 #[test]
842 fn test_get_functions() {
843 let expr = parse_one("SELECT COUNT(*), UPPER(name) FROM t");
844 let funcs = get_functions(&expr);
845 let _ = funcs.len();
850 }
851
852 #[test]
853 fn test_get_aggregate_functions() {
854 let expr = parse_one("SELECT COUNT(*), SUM(x) FROM t");
855 let aggs = get_aggregate_functions(&expr);
856 assert!(
857 aggs.len() >= 2,
858 "Expected at least 2 aggregates, got {}",
859 aggs.len()
860 );
861 }
862}