@@ -30,6 +30,14 @@ pub use self::value::Value;
30
30
31
31
pub use self :: sql_operator:: SQLOperator ;
32
32
33
+ /// Like `vec.join(", ")`, but for any types implementing ToString.
34
+ fn comma_separated_string < T : ToString > ( vec : & [ T ] ) -> String {
35
+ vec. iter ( )
36
+ . map ( T :: to_string)
37
+ . collect :: < Vec < String > > ( )
38
+ . join ( ", " )
39
+ }
40
+
33
41
/// Identifier name, in the originally quoted form (e.g. `"id"`)
34
42
pub type SQLIdent = String ;
35
43
@@ -46,7 +54,7 @@ pub enum ASTNode {
46
54
/// Qualified wildcard, e.g. `alias.*` or `schema.table.*`.
47
55
/// (Same caveats apply to SQLQualifiedWildcard as to SQLWildcard.)
48
56
SQLQualifiedWildcard ( Vec < SQLIdent > ) ,
49
- /// Multi part identifier e.g. `myschema.dbo.mytable `
57
+ /// Multi- part identifier, e.g. `table_alias.column` or `schema.table.col `
50
58
SQLCompoundIdentifier ( Vec < SQLIdent > ) ,
51
59
/// `IS NULL` expression
52
60
SQLIsNull ( Box < ASTNode > ) ,
@@ -92,8 +100,11 @@ pub enum ASTNode {
92
100
/// SQLValue
93
101
SQLValue ( Value ) ,
94
102
/// Scalar function call e.g. `LEFT(foo, 5)`
95
- /// TODO: this can be a compound SQLObjectName as well (for UDFs)
96
- SQLFunction { id : SQLIdent , args : Vec < ASTNode > } ,
103
+ SQLFunction {
104
+ name : SQLObjectName ,
105
+ args : Vec < ASTNode > ,
106
+ over : Option < SQLWindowSpec > ,
107
+ } ,
97
108
/// CASE [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] END
98
109
SQLCase {
99
110
// TODO: support optional operand for "simple case"
@@ -123,10 +134,7 @@ impl ToString for ASTNode {
123
134
"{} {}IN ({})" ,
124
135
expr. as_ref( ) . to_string( ) ,
125
136
if * negated { "NOT " } else { "" } ,
126
- list. iter( )
127
- . map( |a| a. to_string( ) )
128
- . collect:: <Vec <String >>( )
129
- . join( ", " )
137
+ comma_separated_string( list)
130
138
) ,
131
139
ASTNode :: SQLInSubquery {
132
140
expr,
@@ -166,14 +174,13 @@ impl ToString for ASTNode {
166
174
format ! ( "{} {}" , operator. to_string( ) , expr. as_ref( ) . to_string( ) )
167
175
}
168
176
ASTNode :: SQLValue ( v) => v. to_string ( ) ,
169
- ASTNode :: SQLFunction { id, args } => format ! (
170
- "{}({})" ,
171
- id,
172
- args. iter( )
173
- . map( |a| a. to_string( ) )
174
- . collect:: <Vec <String >>( )
175
- . join( ", " )
176
- ) ,
177
+ ASTNode :: SQLFunction { name, args, over } => {
178
+ let mut s = format ! ( "{}({})" , name. to_string( ) , comma_separated_string( args) ) ;
179
+ if let Some ( o) = over {
180
+ s += & format ! ( " OVER ({})" , o. to_string( ) )
181
+ }
182
+ s
183
+ }
177
184
ASTNode :: SQLCase {
178
185
conditions,
179
186
results,
@@ -198,6 +205,116 @@ impl ToString for ASTNode {
198
205
}
199
206
}
200
207
208
+ /// A window specification (i.e. `OVER (PARTITION BY .. ORDER BY .. etc.)`)
209
+ #[ derive( Debug , Clone , PartialEq ) ]
210
+ pub struct SQLWindowSpec {
211
+ pub partition_by : Vec < ASTNode > ,
212
+ pub order_by : Vec < SQLOrderByExpr > ,
213
+ pub window_frame : Option < SQLWindowFrame > ,
214
+ }
215
+
216
+ impl ToString for SQLWindowSpec {
217
+ fn to_string ( & self ) -> String {
218
+ let mut clauses = vec ! [ ] ;
219
+ if !self . partition_by . is_empty ( ) {
220
+ clauses. push ( format ! (
221
+ "PARTITION BY {}" ,
222
+ comma_separated_string( & self . partition_by)
223
+ ) )
224
+ } ;
225
+ if !self . order_by . is_empty ( ) {
226
+ clauses. push ( format ! (
227
+ "ORDER BY {}" ,
228
+ comma_separated_string( & self . order_by)
229
+ ) )
230
+ } ;
231
+ if let Some ( window_frame) = & self . window_frame {
232
+ if let Some ( end_bound) = & window_frame. end_bound {
233
+ clauses. push ( format ! (
234
+ "{} BETWEEN {} AND {}" ,
235
+ window_frame. units. to_string( ) ,
236
+ window_frame. start_bound. to_string( ) ,
237
+ end_bound. to_string( )
238
+ ) ) ;
239
+ } else {
240
+ clauses. push ( format ! (
241
+ "{} {}" ,
242
+ window_frame. units. to_string( ) ,
243
+ window_frame. start_bound. to_string( )
244
+ ) ) ;
245
+ }
246
+ }
247
+ clauses. join ( " " )
248
+ }
249
+ }
250
+
251
+ /// Specifies the data processed by a window function, e.g.
252
+ /// `RANGE UNBOUNDED PRECEDING` or `ROWS BETWEEN 5 PRECEDING AND CURRENT ROW`.
253
+ #[ derive( Debug , Clone , PartialEq ) ]
254
+ pub struct SQLWindowFrame {
255
+ pub units : SQLWindowFrameUnits ,
256
+ pub start_bound : SQLWindowFrameBound ,
257
+ /// The right bound of the `BETWEEN .. AND` clause.
258
+ pub end_bound : Option < SQLWindowFrameBound > ,
259
+ // TBD: EXCLUDE
260
+ }
261
+
262
+ #[ derive( Debug , Clone , PartialEq ) ]
263
+ pub enum SQLWindowFrameUnits {
264
+ Rows ,
265
+ Range ,
266
+ Groups ,
267
+ }
268
+
269
+ impl ToString for SQLWindowFrameUnits {
270
+ fn to_string ( & self ) -> String {
271
+ match self {
272
+ SQLWindowFrameUnits :: Rows => "ROWS" . to_string ( ) ,
273
+ SQLWindowFrameUnits :: Range => "RANGE" . to_string ( ) ,
274
+ SQLWindowFrameUnits :: Groups => "GROUPS" . to_string ( ) ,
275
+ }
276
+ }
277
+ }
278
+
279
+ impl FromStr for SQLWindowFrameUnits {
280
+ type Err = ParserError ;
281
+
282
+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
283
+ match s {
284
+ "ROWS" => Ok ( SQLWindowFrameUnits :: Rows ) ,
285
+ "RANGE" => Ok ( SQLWindowFrameUnits :: Range ) ,
286
+ "GROUPS" => Ok ( SQLWindowFrameUnits :: Groups ) ,
287
+ _ => Err ( ParserError :: ParserError ( format ! (
288
+ "Expected ROWS, RANGE, or GROUPS, found: {}" ,
289
+ s
290
+ ) ) ) ,
291
+ }
292
+ }
293
+ }
294
+
295
+ #[ derive( Debug , Clone , PartialEq ) ]
296
+ pub enum SQLWindowFrameBound {
297
+ /// "CURRENT ROW"
298
+ CurrentRow ,
299
+ /// "<N> PRECEDING" or "UNBOUNDED PRECEDING"
300
+ Preceding ( Option < u64 > ) ,
301
+ /// "<N> FOLLOWING" or "UNBOUNDED FOLLOWING". This can only appear in
302
+ /// SQLWindowFrame::end_bound.
303
+ Following ( Option < u64 > ) ,
304
+ }
305
+
306
+ impl ToString for SQLWindowFrameBound {
307
+ fn to_string ( & self ) -> String {
308
+ match self {
309
+ SQLWindowFrameBound :: CurrentRow => "CURRENT ROW" . to_string ( ) ,
310
+ SQLWindowFrameBound :: Preceding ( None ) => "UNBOUNDED PRECEDING" . to_string ( ) ,
311
+ SQLWindowFrameBound :: Following ( None ) => "UNBOUNDED FOLLOWING" . to_string ( ) ,
312
+ SQLWindowFrameBound :: Preceding ( Some ( n) ) => format ! ( "{} PRECEDING" , n) ,
313
+ SQLWindowFrameBound :: Following ( Some ( n) ) => format ! ( "{} FOLLOWING" , n) ,
314
+ }
315
+ }
316
+ }
317
+
201
318
/// A top-level statement (SELECT, INSERT, CREATE, etc.)
202
319
#[ derive( Debug , Clone , PartialEq ) ]
203
320
pub enum SQLStatement {
@@ -279,11 +396,7 @@ impl ToString for SQLStatement {
279
396
" VALUES({})" ,
280
397
values
281
398
. iter( )
282
- . map( |row| row
283
- . iter( )
284
- . map( |c| c. to_string( ) )
285
- . collect:: <Vec <String >>( )
286
- . join( ", " ) )
399
+ . map( |row| comma_separated_string( row) )
287
400
. collect:: <Vec <String >>( )
288
401
. join( ", " )
289
402
) ;
@@ -296,15 +409,8 @@ impl ToString for SQLStatement {
296
409
values,
297
410
} => {
298
411
let mut s = format ! ( "COPY {}" , table_name. to_string( ) ) ;
299
- if columns. len ( ) > 0 {
300
- s += & format ! (
301
- " ({})" ,
302
- columns
303
- . iter( )
304
- . map( |c| c. to_string( ) )
305
- . collect:: <Vec <String >>( )
306
- . join( ", " )
307
- ) ;
412
+ if !columns. is_empty ( ) {
413
+ s += & format ! ( " ({})" , comma_separated_string( columns) ) ;
308
414
}
309
415
s += " FROM stdin; " ;
310
416
if !values. is_empty ( ) {
@@ -326,15 +432,8 @@ impl ToString for SQLStatement {
326
432
selection,
327
433
} => {
328
434
let mut s = format ! ( "UPDATE {}" , table_name. to_string( ) ) ;
329
- if assignments. len ( ) > 0 {
330
- s += & format ! (
331
- "{}" ,
332
- assignments
333
- . iter( )
334
- . map( |ass| ass. to_string( ) )
335
- . collect:: <Vec <String >>( )
336
- . join( ", " )
337
- ) ;
435
+ if !assignments. is_empty ( ) {
436
+ s += & comma_separated_string ( assignments) ;
338
437
}
339
438
if let Some ( selection) = selection {
340
439
s += & format ! ( " WHERE {}" , selection. to_string( ) ) ;
@@ -373,22 +472,14 @@ impl ToString for SQLStatement {
373
472
} if * external => format ! (
374
473
"CREATE EXTERNAL TABLE {} ({}) STORED AS {} LOCATION '{}'" ,
375
474
name. to_string( ) ,
376
- columns
377
- . iter( )
378
- . map( |c| c. to_string( ) )
379
- . collect:: <Vec <String >>( )
380
- . join( ", " ) ,
381
- file_format. as_ref( ) . map( |f| f. to_string( ) ) . unwrap( ) ,
475
+ comma_separated_string( columns) ,
476
+ file_format. as_ref( ) . unwrap( ) . to_string( ) ,
382
477
location. as_ref( ) . unwrap( )
383
478
) ,
384
479
SQLStatement :: SQLCreateTable { name, columns, .. } => format ! (
385
480
"CREATE TABLE {} ({})" ,
386
481
name. to_string( ) ,
387
- columns
388
- . iter( )
389
- . map( |c| c. to_string( ) )
390
- . collect:: <Vec <String >>( )
391
- . join( ", " )
482
+ comma_separated_string( columns)
392
483
) ,
393
484
SQLStatement :: SQLAlterTable { name, operation } => {
394
485
format ! ( "ALTER TABLE {} {}" , name. to_string( ) , operation. to_string( ) )
0 commit comments