@@ -17,19 +17,52 @@ where
1717 ) -> SqlTokens < ' a > {
1818 let mut tokens = SqlTokens :: default ( ) ;
1919
20- let select_ast =
21- transient:: Downcast :: downcast_ref :: < SelectAst > ( ast. as_any ( ) ) . expect ( "Handle this" ) ;
20+ let select_ast = transient:: Downcast :: downcast_ref :: < SelectAst > ( ast. as_any ( ) ) . expect (
21+ "[emitSelect] - Handle this propagating result and introducing custom error types" ,
22+ ) ;
2223
2324 tokens. keyword ( Keyword :: Select ) ;
2425
2526 __impl:: emit_columns ( select_ast, & mut tokens) ;
2627 __impl:: emit_from ( base_ast, & mut tokens) ;
28+ __impl:: emit_joins ( select_ast, & mut tokens) ;
29+ __impl:: emit_group_by ( select_ast, & mut tokens) ;
30+ __impl:: emit_having ( select_ast, & mut tokens) ;
31+ __impl:: emit_order_by ( select_ast, & mut tokens) ;
32+ __impl:: emit_limit ( select_ast, & mut tokens) ;
33+ __impl:: emit_offset ( select_ast, & mut tokens) ;
2734
2835 tokens
2936 }
3037}
3138
32- /// DOC me this
39+ /// Emits a `SELECT` query from the provided AST nodes.
40+ ///
41+ /// This trait is implemented as a blanket implementation for any type that
42+ /// implements [`SqlEmitter`]. It converts the high level [`SelectAst`] plus the
43+ /// shared [`BaseAst`] information into a sequence of [`SqlTokens`].
44+ ///
45+ /// The emission process is intentionally split into small stateless helpers
46+ /// to keep the codegen predictable and allow the compiler to aggressively
47+ /// inline them.
48+ ///
49+ /// Expected clause order:
50+ ///
51+ /// SELECT
52+ /// -> columns
53+ /// -> FROM
54+ /// -> JOINs
55+ /// -> GROUP BY
56+ /// -> HAVING
57+ /// -> ORDER BY
58+ /// -> LIMIT
59+ /// -> OFFSET
60+ ///
61+ /// `BaseAst` contains elements shared across query kinds (for example the
62+ /// root table), while `SelectAst` contains clauses specific to SELECT queries.
63+ ///
64+ /// Implementors normally do not override this method and instead rely on the
65+ /// default blanket implementation.
3366pub trait EmitSelect < ' a > : SqlEmitter < ' a > {
3467 fn emit_select ( & mut self , ast : & impl AstProcessor < ' a > , base_ast : & BaseAst < ' a > )
3568 -> SqlTokens < ' a > ;
@@ -50,4 +83,198 @@ mod __impl {
5083 tokens. keyword ( Keyword :: From ) ;
5184 tokens. extend ( base_ast. table . to_tokens ( ) ) ;
5285 }
86+
87+ pub ( crate ) fn emit_joins < ' a > ( ast : & SelectAst < ' a > , tokens : & mut SqlTokens < ' a > ) {
88+ for join in & ast. joins {
89+ tokens. extend ( join. to_tokens ( ) ) ;
90+ }
91+ }
92+
93+ pub ( crate ) fn emit_group_by < ' a > ( ast : & SelectAst < ' a > , tokens : & mut SqlTokens < ' a > ) {
94+ if let Some ( group_by) = & ast. group_by {
95+ tokens. keyword ( Keyword :: GroupBy ) ;
96+ helpers:: emit_columns ( & group_by, tokens) ;
97+ }
98+ }
99+
100+ pub ( crate ) fn emit_having < ' a > ( ast : & SelectAst < ' a > , tokens : & mut SqlTokens < ' a > ) {
101+ if let Some ( having) = & ast. having {
102+ tokens. keyword ( Keyword :: Having ) ;
103+ tokens. extend ( having. to_tokens ( ) ) ;
104+ }
105+ }
106+
107+ pub ( crate ) fn emit_order_by < ' a > ( ast : & SelectAst < ' a > , tokens : & mut SqlTokens < ' a > ) {
108+ if let Some ( order_by) = & ast. order_by {
109+ tokens. extend ( order_by. to_tokens ( ) ) ;
110+ }
111+ }
112+
113+ pub ( crate ) fn emit_limit < ' a > ( ast : & SelectAst < ' a > , tokens : & mut SqlTokens < ' a > ) {
114+ if let Some ( limit) = ast. limit {
115+ tokens. keyword ( Keyword :: Limit ) ;
116+ tokens. numeric ( limit) ;
117+ }
118+ }
119+
120+ pub ( crate ) fn emit_offset < ' a > ( ast : & SelectAst < ' a > , tokens : & mut SqlTokens < ' a > ) {
121+ if let Some ( offset) = ast. offset {
122+ tokens. keyword ( Keyword :: Offset ) ;
123+ tokens. numeric ( offset) ;
124+ }
125+ }
126+ }
127+
128+ #[ cfg( test) ]
129+ mod tests {
130+ use crate :: connection:: database_type:: DatabaseType ;
131+ use crate :: query:: operators:: Comp ;
132+ use crate :: query:: querybuilder:: syntax:: ast:: BaseAst ;
133+ use crate :: query:: querybuilder:: syntax:: ast:: select:: SelectAst ;
134+ use crate :: query:: querybuilder:: syntax:: column:: ColumnRef ;
135+ use crate :: query:: querybuilder:: syntax:: dialect:: StandardDialect ;
136+ use crate :: query:: querybuilder:: syntax:: emitter:: SqlEmitter ;
137+ use crate :: query:: querybuilder:: syntax:: emitter:: types:: select:: EmitSelect ;
138+ use crate :: query:: querybuilder:: syntax:: order:: OrderByClause ;
139+ use crate :: query:: querybuilder:: syntax:: writer:: TokenWriter ;
140+
141+ struct TestEmitter ;
142+ impl < ' a > SqlEmitter < ' a > for TestEmitter {
143+ type Dialect = StandardDialect ;
144+ }
145+
146+ fn col ( name : & ' _ str ) -> ColumnRef < ' _ > {
147+ ColumnRef :: from ( name)
148+ }
149+
150+ fn render < ' a > ( ast : & SelectAst < ' a > , base_ast : & BaseAst < ' a > ) -> String {
151+ let mut emitter = TestEmitter ;
152+ let tokens = emitter. emit_select ( ast, base_ast) ;
153+ TokenWriter :: new ( )
154+ . render ( & tokens, DatabaseType :: Deferred )
155+ . unwrap ( )
156+ }
157+
158+ #[ test]
159+ fn emits_select_with_columns_and_from ( ) {
160+ let mut ast = SelectAst :: new ( ) ;
161+ ast. columns = vec ! [ col( "id" ) , col( "name" ) ] ;
162+
163+ let base_ast = BaseAst {
164+ table : "users" . into ( ) ,
165+ ..Default :: default ( )
166+ } ;
167+
168+ let sql = render ( & ast, & base_ast) ;
169+
170+ assert_eq ! ( sql, "SELECT id, name FROM users" ) ;
171+ }
172+
173+ #[ test]
174+ fn emits_select_with_order_by_limit_and_offset ( ) {
175+ let mut ast = SelectAst :: new ( ) ;
176+ ast. columns = vec ! [ col( "id" ) ] ;
177+ ast. order_by = Some ( OrderByClause :: new ( "id" , true ) ) ;
178+ ast. limit = Some ( 10 ) ;
179+ ast. offset = Some ( 20 ) ;
180+
181+ let base_ast = BaseAst {
182+ table : "users" . into ( ) ,
183+ ..Default :: default ( )
184+ } ;
185+
186+ let sql = render ( & ast, & base_ast) ;
187+
188+ assert_eq ! (
189+ sql,
190+ "SELECT id FROM users ORDER BY id DESC LIMIT 10 OFFSET 20"
191+ ) ;
192+ }
193+
194+ #[ test]
195+ fn emits_select_without_optional_clauses ( ) {
196+ let mut ast = SelectAst :: new ( ) ;
197+ ast. columns = vec ! [ col( "*" ) ] ;
198+
199+ let base_ast = BaseAst {
200+ table : "users" . into ( ) ,
201+ ..Default :: default ( )
202+ } ;
203+
204+ let sql = render ( & ast, & base_ast) ;
205+
206+ assert_eq ! ( sql, "SELECT * FROM users" ) ;
207+ }
208+
209+ #[ test]
210+ fn emits_group_by_when_present ( ) {
211+ let mut ast = SelectAst :: new ( ) ;
212+ ast. columns = vec ! [ col( "country" ) ] ;
213+ ast. group_by = Some ( vec ! [ col( "country" ) ] ) ;
214+
215+ let base_ast = BaseAst {
216+ table : "users" . into ( ) ,
217+ ..Default :: default ( )
218+ } ;
219+
220+ let sql = render ( & ast, & base_ast) ;
221+
222+ assert_eq ! ( sql, "SELECT country FROM users GROUP BY country" ) ;
223+ }
224+
225+ #[ test]
226+ fn emits_select_with_all_join_kinds ( ) {
227+ use crate :: query:: querybuilder:: syntax:: join:: { JoinClause , JoinKind } ;
228+
229+ let mut ast = SelectAst :: new ( ) ;
230+ ast. columns = vec ! [ col( "users.id" ) , col( "profiles.bio" ) , col( "roles.name" ) ] ;
231+
232+ ast. joins = vec ! [
233+ JoinClause :: new(
234+ JoinKind :: Inner ,
235+ "profiles" . into( ) ,
236+ col( "users.id" ) ,
237+ Comp :: Eq ,
238+ col( "profiles.user_id" ) ,
239+ ) ,
240+ JoinClause :: new(
241+ JoinKind :: Left ,
242+ "roles" . into( ) ,
243+ col( "users.role_id" ) ,
244+ Comp :: Eq ,
245+ col( "roles.id" ) ,
246+ ) ,
247+ JoinClause :: new(
248+ JoinKind :: Right ,
249+ "teams" . into( ) ,
250+ col( "users.team_id" ) ,
251+ Comp :: Eq ,
252+ col( "teams.id" ) ,
253+ ) ,
254+ JoinClause :: new(
255+ JoinKind :: FullOuter ,
256+ "permissions" . into( ) ,
257+ col( "users.id" ) ,
258+ Comp :: Eq ,
259+ col( "permissions.user_id" ) ,
260+ ) ,
261+ ] ;
262+
263+ let base_ast = BaseAst {
264+ table : "users" . into ( ) ,
265+ ..Default :: default ( )
266+ } ;
267+
268+ let sql = render ( & ast, & base_ast) ;
269+
270+ assert_eq ! (
271+ sql,
272+ "SELECT users.id, profiles.bio, roles.name \
273+ FROM users \
274+ INNER JOIN profiles ON users.id = profiles.user_id \
275+ LEFT JOIN roles ON users.role_id = roles.id \
276+ RIGHT JOIN teams ON users.team_id = teams.id \
277+ FULL OUTER JOIN permissions ON users.id = permissions.user_id"
278+ ) ;
279+ }
53280}
0 commit comments