From 59dbd2827921ca84568b7f3a8ad8c163a340e48b Mon Sep 17 00:00:00 2001 From: Sidney Cammeresi Date: Fri, 17 Jul 2026 11:30:31 -0700 Subject: [PATCH 1/2] parser: Add visitors for ORDER BY clauses --- src/ast/query.rs | 2 + src/ast/visitor.rs | 114 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/ast/query.rs b/src/ast/query.rs index fdcacddf7..2dd04db28 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -2888,6 +2888,7 @@ pub enum OrderByKind { #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +#[cfg_attr(feature = "visitor", visit(with = "visit_order_by"))] /// Represents an `ORDER BY` clause with its kind and optional `INTERPOLATE`. pub struct OrderBy { /// The kind of ordering (expressions or `ALL`). @@ -2924,6 +2925,7 @@ impl fmt::Display for OrderBy { #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +#[cfg_attr(feature = "visitor", visit(with = "visit_order_by_expr"))] pub struct OrderByExpr { /// The expression to order by. pub expr: Expr, diff --git a/src/ast/visitor.rs b/src/ast/visitor.rs index c70e83ec6..732710075 100644 --- a/src/ast/visitor.rs +++ b/src/ast/visitor.rs @@ -21,7 +21,10 @@ use alloc::{boxed::Box, string::String, vec::Vec}; use core::ops::ControlFlow; -use crate::ast::{Expr, Ident, ObjectName, Query, Select, Statement, TableFactor, ValueWithSpan}; +use crate::ast::{ + Expr, Ident, ObjectName, OrderBy, OrderByExpr, Query, Select, Statement, TableFactor, + ValueWithSpan, +}; /// A type that can be visited by a [`Visitor`]. See [`Visitor`] for /// recursively visiting parsed SQL statements. @@ -279,6 +282,32 @@ pub trait Visitor { fn post_visit_ident(&mut self, _ident: &Ident) -> ControlFlow { ControlFlow::Continue(()) } + + /// Invoked for any `ORDER BY` clauses that appear in the AST before visiting children + fn pre_visit_order_by(&mut self, _order_by: &OrderBy) -> ControlFlow { + ControlFlow::Continue(()) + } + + /// Invoked for any `ORDER BY` clauses that appear in the AST after visiting children + fn post_visit_order_by(&mut self, _order_by: &OrderBy) -> ControlFlow { + ControlFlow::Continue(()) + } + + /// Invoked for any `ORDER BY` expressions that appear in the AST before visiting children + fn pre_visit_order_by_expr( + &mut self, + _order_by_expr: &OrderByExpr, + ) -> ControlFlow { + ControlFlow::Continue(()) + } + + /// Invoked for any `ORDER BY` expressions that appear in the AST after visiting children + fn post_visit_order_by_expr( + &mut self, + _order_by_expr: &OrderByExpr, + ) -> ControlFlow { + ControlFlow::Continue(()) + } } /// A visitor that can be used to mutate an AST tree. @@ -417,6 +446,32 @@ pub trait VisitorMut { fn post_visit_ident(&mut self, _ident: &mut Ident) -> ControlFlow { ControlFlow::Continue(()) } + + /// Invoked for any `ORDER BY` clauses that appear in the AST before visiting children + fn pre_visit_order_by(&mut self, _order_by: &mut OrderBy) -> ControlFlow { + ControlFlow::Continue(()) + } + + /// Invoked for any `ORDER BY` clauses that appear in the AST after visiting children + fn post_visit_order_by(&mut self, _order_by: &mut OrderBy) -> ControlFlow { + ControlFlow::Continue(()) + } + + /// Invoked for any `ORDER BY` expressions that appear in the AST before visiting children + fn pre_visit_order_by_expr( + &mut self, + _order_by_expr: &mut OrderByExpr, + ) -> ControlFlow { + ControlFlow::Continue(()) + } + + /// Invoked for any `ORDER BY` expressions that appear in the AST after visiting children + fn post_visit_order_by_expr( + &mut self, + _order_by_expr: &mut OrderByExpr, + ) -> ControlFlow { + ControlFlow::Continue(()) + } } struct RelationVisitor(F); @@ -809,6 +864,34 @@ mod tests { self.visited.push(format!("POST: STATEMENT: {statement}")); ControlFlow::Continue(()) } + + fn pre_visit_order_by(&mut self, order_by: &OrderBy) -> ControlFlow { + self.visited.push(format!("PRE: ORDER BY: {order_by}")); + ControlFlow::Continue(()) + } + + fn post_visit_order_by(&mut self, order_by: &OrderBy) -> ControlFlow { + self.visited.push(format!("POST: ORDER BY: {order_by}")); + ControlFlow::Continue(()) + } + + fn pre_visit_order_by_expr( + &mut self, + order_by_expr: &OrderByExpr, + ) -> ControlFlow { + self.visited + .push(format!("PRE: ORDER BY EXPR: {order_by_expr}")); + ControlFlow::Continue(()) + } + + fn post_visit_order_by_expr( + &mut self, + order_by_expr: &OrderByExpr, + ) -> ControlFlow { + self.visited + .push(format!("POST: ORDER BY EXPR: {order_by_expr}")); + ControlFlow::Continue(()) + } } fn do_visit>(sql: &str, visitor: &mut V) -> Statement { @@ -985,8 +1068,12 @@ mod tests { "POST: EXPR: 'APR'", "POST: TABLE FACTOR: monthly_sales PIVOT(SUM(a.amount) FOR a.MONTH IN ('JAN', 'FEB', 'MAR', 'APR')) AS p (c, d)", "POST: SELECT: SELECT * FROM monthly_sales PIVOT(SUM(a.amount) FOR a.MONTH IN ('JAN', 'FEB', 'MAR', 'APR')) AS p (c, d)", + "PRE: ORDER BY: ORDER BY EMPID", + "PRE: ORDER BY EXPR: EMPID", "PRE: EXPR: EMPID", "POST: EXPR: EMPID", + "POST: ORDER BY EXPR: EMPID", + "POST: ORDER BY: ORDER BY EMPID", "POST: QUERY: SELECT * FROM monthly_sales PIVOT(SUM(a.amount) FOR a.MONTH IN ('JAN', 'FEB', 'MAR', 'APR')) AS p (c, d) ORDER BY EMPID", "POST: STATEMENT: SELECT * FROM monthly_sales PIVOT(SUM(a.amount) FOR a.MONTH IN ('JAN', 'FEB', 'MAR', 'APR')) AS p (c, d) ORDER BY EMPID", ] @@ -1000,6 +1087,31 @@ mod tests { "POST: STATEMENT: SHOW COLUMNS FROM t1", ], ), + ( + "SELECT * FROM t1 ORDER BY a DESC, b", + vec![ + "PRE: STATEMENT: SELECT * FROM t1 ORDER BY a DESC, b", + "PRE: QUERY: SELECT * FROM t1 ORDER BY a DESC, b", + "PRE: SELECT: SELECT * FROM t1", + "PRE: TABLE FACTOR: t1", + "PRE: RELATION: t1", + "POST: RELATION: t1", + "POST: TABLE FACTOR: t1", + "POST: SELECT: SELECT * FROM t1", + "PRE: ORDER BY: ORDER BY a DESC, b", + "PRE: ORDER BY EXPR: a DESC", + "PRE: EXPR: a", + "POST: EXPR: a", + "POST: ORDER BY EXPR: a DESC", + "PRE: ORDER BY EXPR: b", + "PRE: EXPR: b", + "POST: EXPR: b", + "POST: ORDER BY EXPR: b", + "POST: ORDER BY: ORDER BY a DESC, b", + "POST: QUERY: SELECT * FROM t1 ORDER BY a DESC, b", + "POST: STATEMENT: SELECT * FROM t1 ORDER BY a DESC, b", + ], + ), ]; for (sql, expected) in tests { let mut visitor = TestVisitor::default(); From c6313a55c70040a6281cc1a4401bdce50e2a503f Mon Sep 17 00:00:00 2001 From: Sidney Cammeresi Date: Fri, 17 Jul 2026 11:57:34 -0700 Subject: [PATCH 2/2] parser: Add visitors for GROUP BY clauses --- src/ast/query.rs | 1 + src/ast/visitor.rs | 79 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/ast/query.rs b/src/ast/query.rs index 2dd04db28..c683d66c0 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -3778,6 +3778,7 @@ impl fmt::Display for GroupByWithModifier { #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +#[cfg_attr(feature = "visitor", visit(with = "visit_group_by"))] /// Represents the two syntactic forms that `GROUP BY` can take, including /// `GROUP BY ALL` with optional modifiers and ordinary `GROUP BY `. pub enum GroupByExpr { diff --git a/src/ast/visitor.rs b/src/ast/visitor.rs index 732710075..9011a94d5 100644 --- a/src/ast/visitor.rs +++ b/src/ast/visitor.rs @@ -22,8 +22,8 @@ use alloc::{boxed::Box, string::String, vec::Vec}; use core::ops::ControlFlow; use crate::ast::{ - Expr, Ident, ObjectName, OrderBy, OrderByExpr, Query, Select, Statement, TableFactor, - ValueWithSpan, + Expr, GroupByExpr, Ident, ObjectName, OrderBy, OrderByExpr, Query, Select, Statement, + TableFactor, ValueWithSpan, }; /// A type that can be visited by a [`Visitor`]. See [`Visitor`] for @@ -308,6 +308,16 @@ pub trait Visitor { ) -> ControlFlow { ControlFlow::Continue(()) } + + /// Invoked for any `GROUP BY` clauses that appear in the AST before visiting children + fn pre_visit_group_by(&mut self, _group_by: &GroupByExpr) -> ControlFlow { + ControlFlow::Continue(()) + } + + /// Invoked for any `GROUP BY` clauses that appear in the AST after visiting children + fn post_visit_group_by(&mut self, _group_by: &GroupByExpr) -> ControlFlow { + ControlFlow::Continue(()) + } } /// A visitor that can be used to mutate an AST tree. @@ -472,6 +482,16 @@ pub trait VisitorMut { ) -> ControlFlow { ControlFlow::Continue(()) } + + /// Invoked for any `GROUP BY` clauses that appear in the AST before visiting children + fn pre_visit_group_by(&mut self, _group_by: &mut GroupByExpr) -> ControlFlow { + ControlFlow::Continue(()) + } + + /// Invoked for any `GROUP BY` clauses that appear in the AST after visiting children + fn post_visit_group_by(&mut self, _group_by: &mut GroupByExpr) -> ControlFlow { + ControlFlow::Continue(()) + } } struct RelationVisitor(F); @@ -892,6 +912,16 @@ mod tests { .push(format!("POST: ORDER BY EXPR: {order_by_expr}")); ControlFlow::Continue(()) } + + fn pre_visit_group_by(&mut self, group_by: &GroupByExpr) -> ControlFlow { + self.visited.push(format!("PRE: GROUP BY: {group_by}")); + ControlFlow::Continue(()) + } + + fn post_visit_group_by(&mut self, group_by: &GroupByExpr) -> ControlFlow { + self.visited.push(format!("POST: GROUP BY: {group_by}")); + ControlFlow::Continue(()) + } } fn do_visit>(sql: &str, visitor: &mut V) -> Statement { @@ -920,6 +950,8 @@ mod tests { "PRE: RELATION: table_name", "POST: RELATION: table_name", "POST: TABLE FACTOR: table_name AS my_table", + "PRE: GROUP BY: GROUP BY ", + "POST: GROUP BY: GROUP BY ", "POST: SELECT: SELECT * FROM table_name AS my_table", "POST: QUERY: SELECT * FROM table_name AS my_table", "POST: STATEMENT: SELECT * FROM table_name AS my_table", @@ -945,6 +977,8 @@ mod tests { "PRE: EXPR: t2.t1_id", "POST: EXPR: t2.t1_id", "POST: EXPR: t1.id = t2.t1_id", + "PRE: GROUP BY: GROUP BY ", + "POST: GROUP BY: GROUP BY ", "POST: SELECT: SELECT * FROM t1 JOIN t2 ON t1.id = t2.t1_id", "POST: QUERY: SELECT * FROM t1 JOIN t2 ON t1.id = t2.t1_id", "POST: STATEMENT: SELECT * FROM t1 JOIN t2 ON t1.id = t2.t1_id", @@ -969,9 +1003,13 @@ mod tests { "PRE: RELATION: t2", "POST: RELATION: t2", "POST: TABLE FACTOR: t2", + "PRE: GROUP BY: GROUP BY ", + "POST: GROUP BY: GROUP BY ", "POST: SELECT: SELECT column FROM t2", "POST: QUERY: SELECT column FROM t2", "POST: EXPR: EXISTS (SELECT column FROM t2)", + "PRE: GROUP BY: GROUP BY ", + "POST: GROUP BY: GROUP BY ", "POST: SELECT: SELECT * FROM t1 WHERE EXISTS (SELECT column FROM t2)", "POST: QUERY: SELECT * FROM t1 WHERE EXISTS (SELECT column FROM t2)", "POST: STATEMENT: SELECT * FROM t1 WHERE EXISTS (SELECT column FROM t2)", @@ -996,9 +1034,13 @@ mod tests { "PRE: RELATION: t2", "POST: RELATION: t2", "POST: TABLE FACTOR: t2", + "PRE: GROUP BY: GROUP BY ", + "POST: GROUP BY: GROUP BY ", "POST: SELECT: SELECT column FROM t2", "POST: QUERY: SELECT column FROM t2", "POST: EXPR: EXISTS (SELECT column FROM t2)", + "PRE: GROUP BY: GROUP BY ", + "POST: GROUP BY: GROUP BY ", "POST: SELECT: SELECT * FROM t1 WHERE EXISTS (SELECT column FROM t2)", "POST: QUERY: SELECT * FROM t1 WHERE EXISTS (SELECT column FROM t2)", "POST: STATEMENT: SELECT * FROM t1 WHERE EXISTS (SELECT column FROM t2)", @@ -1023,15 +1065,21 @@ mod tests { "PRE: RELATION: t2", "POST: RELATION: t2", "POST: TABLE FACTOR: t2", + "PRE: GROUP BY: GROUP BY ", + "POST: GROUP BY: GROUP BY ", "POST: SELECT: SELECT column FROM t2", "POST: QUERY: SELECT column FROM t2", "POST: EXPR: EXISTS (SELECT column FROM t2)", + "PRE: GROUP BY: GROUP BY ", + "POST: GROUP BY: GROUP BY ", "POST: SELECT: SELECT * FROM t1 WHERE EXISTS (SELECT column FROM t2)", "PRE: SELECT: SELECT * FROM t3", "PRE: TABLE FACTOR: t3", "PRE: RELATION: t3", "POST: RELATION: t3", "POST: TABLE FACTOR: t3", + "PRE: GROUP BY: GROUP BY ", + "POST: GROUP BY: GROUP BY ", "POST: SELECT: SELECT * FROM t3", "POST: QUERY: SELECT * FROM t1 WHERE EXISTS (SELECT column FROM t2) UNION SELECT * FROM t3", "POST: STATEMENT: SELECT * FROM t1 WHERE EXISTS (SELECT column FROM t2) UNION SELECT * FROM t3", @@ -1067,6 +1115,8 @@ mod tests { "PRE: EXPR: 'APR'", "POST: EXPR: 'APR'", "POST: TABLE FACTOR: monthly_sales PIVOT(SUM(a.amount) FOR a.MONTH IN ('JAN', 'FEB', 'MAR', 'APR')) AS p (c, d)", + "PRE: GROUP BY: GROUP BY ", + "POST: GROUP BY: GROUP BY ", "POST: SELECT: SELECT * FROM monthly_sales PIVOT(SUM(a.amount) FOR a.MONTH IN ('JAN', 'FEB', 'MAR', 'APR')) AS p (c, d)", "PRE: ORDER BY: ORDER BY EMPID", "PRE: ORDER BY EXPR: EMPID", @@ -1097,6 +1147,8 @@ mod tests { "PRE: RELATION: t1", "POST: RELATION: t1", "POST: TABLE FACTOR: t1", + "PRE: GROUP BY: GROUP BY ", + "POST: GROUP BY: GROUP BY ", "POST: SELECT: SELECT * FROM t1", "PRE: ORDER BY: ORDER BY a DESC, b", "PRE: ORDER BY EXPR: a DESC", @@ -1112,6 +1164,29 @@ mod tests { "POST: STATEMENT: SELECT * FROM t1 ORDER BY a DESC, b", ], ), + ( + "SELECT a FROM t GROUP BY a, b", + vec![ + "PRE: STATEMENT: SELECT a FROM t GROUP BY a, b", + "PRE: QUERY: SELECT a FROM t GROUP BY a, b", + "PRE: SELECT: SELECT a FROM t GROUP BY a, b", + "PRE: EXPR: a", + "POST: EXPR: a", + "PRE: TABLE FACTOR: t", + "PRE: RELATION: t", + "POST: RELATION: t", + "POST: TABLE FACTOR: t", + "PRE: GROUP BY: GROUP BY a, b", + "PRE: EXPR: a", + "POST: EXPR: a", + "PRE: EXPR: b", + "POST: EXPR: b", + "POST: GROUP BY: GROUP BY a, b", + "POST: SELECT: SELECT a FROM t GROUP BY a, b", + "POST: QUERY: SELECT a FROM t GROUP BY a, b", + "POST: STATEMENT: SELECT a FROM t GROUP BY a, b", + ], + ), ]; for (sql, expected) in tests { let mut visitor = TestVisitor::default();