Skip to content

Commit 6bb2acc

Browse files
authored
Merge pull request #50 from nickolay/window-functions
Support OVER clause for window/analytic functions, add support for qualified function names
2 parents 64b1ea7 + 9a244e0 commit 6bb2acc

File tree

6 files changed

+295
-89
lines changed

6 files changed

+295
-89
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ println!("AST: {:?}", ast);
3030
This outputs
3131

3232
```rust
33-
AST: [SQLSelect(SQLSelect { projection: [SQLIdentifier("a"), SQLIdentifier("b"), SQLValue(Long(123)), SQLFunction { id: "myfunc", args: [SQLIdentifier("b")] }], relation: Some(Table { name: SQLObjectName(["table_1"]), alias: None }), joins: [], selection: Some(SQLBinaryExpr { left: SQLBinaryExpr { left: SQLIdentifier("a"), op: Gt, right: SQLIdentifier("b") }, op: And, right: SQLBinaryExpr { left: SQLIdentifier("b"), op: Lt, right: SQLValue(Long(100)) } }), order_by: Some([SQLOrderByExpr { expr: SQLIdentifier("a"), asc: Some(false) }, SQLOrderByExpr { expr: SQLIdentifier("b"), asc: None }]), group_by: None, having: None, limit: None })]
33+
AST: [SQLSelect(SQLQuery { ctes: [], body: Select(SQLSelect { distinct: false, projection: [UnnamedExpression(SQLIdentifier("a")), UnnamedExpression(SQLIdentifier("b")), UnnamedExpression(SQLValue(Long(123))), UnnamedExpression(SQLFunction { name: SQLObjectName(["myfunc"]), args: [SQLIdentifier("b")], over: None })], relation: Some(Table { name: SQLObjectName(["table_1"]), alias: None }), joins: [], selection: Some(SQLBinaryExpr { left: SQLBinaryExpr { left: SQLIdentifier("a"), op: Gt, right: SQLIdentifier("b") }, op: And, right: SQLBinaryExpr { left: SQLIdentifier("b"), op: Lt, right: SQLValue(Long(100)) } }), group_by: None, having: None }), order_by: Some([SQLOrderByExpr { expr: SQLIdentifier("a"), asc: Some(false) }, SQLOrderByExpr { expr: SQLIdentifier("b"), asc: None }]), limit: None })]
3434
```
3535

3636
## Design

src/dialect/keywords.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ keyword!(
139139
FIRST_VALUE,
140140
FLOAT,
141141
FLOOR,
142+
FOLLOWING,
142143
FOR,
143144
FOREIGN,
144145
FRAME_ROW,
@@ -246,6 +247,7 @@ keyword!(
246247
POSITION_REGEX,
247248
POWER,
248249
PRECEDES,
250+
PRECEDING,
249251
PRECISION,
250252
PREPARE,
251253
PRIMARY,
@@ -333,6 +335,7 @@ keyword!(
333335
TRIM_ARRAY,
334336
TRUE,
335337
UESCAPE,
338+
UNBOUNDED,
336339
UNION,
337340
UNIQUE,
338341
UNKNOWN,
@@ -488,6 +491,7 @@ pub const ALL_KEYWORDS: &[&str] = &[
488491
FIRST_VALUE,
489492
FLOAT,
490493
FLOOR,
494+
FOLLOWING,
491495
FOR,
492496
FOREIGN,
493497
FRAME_ROW,
@@ -595,6 +599,7 @@ pub const ALL_KEYWORDS: &[&str] = &[
595599
POSITION_REGEX,
596600
POWER,
597601
PRECEDES,
602+
PRECEDING,
598603
PRECISION,
599604
PREPARE,
600605
PRIMARY,
@@ -682,6 +687,7 @@ pub const ALL_KEYWORDS: &[&str] = &[
682687
TRIM_ARRAY,
683688
TRUE,
684689
UESCAPE,
690+
UNBOUNDED,
685691
UNION,
686692
UNIQUE,
687693
UNKNOWN,

src/sqlast/mod.rs

Lines changed: 140 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ pub use self::value::Value;
3030

3131
pub use self::sql_operator::SQLOperator;
3232

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+
3341
/// Identifier name, in the originally quoted form (e.g. `"id"`)
3442
pub type SQLIdent = String;
3543

@@ -46,7 +54,7 @@ pub enum ASTNode {
4654
/// Qualified wildcard, e.g. `alias.*` or `schema.table.*`.
4755
/// (Same caveats apply to SQLQualifiedWildcard as to SQLWildcard.)
4856
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`
5058
SQLCompoundIdentifier(Vec<SQLIdent>),
5159
/// `IS NULL` expression
5260
SQLIsNull(Box<ASTNode>),
@@ -92,8 +100,11 @@ pub enum ASTNode {
92100
/// SQLValue
93101
SQLValue(Value),
94102
/// 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+
},
97108
/// CASE [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] END
98109
SQLCase {
99110
// TODO: support optional operand for "simple case"
@@ -123,10 +134,7 @@ impl ToString for ASTNode {
123134
"{} {}IN ({})",
124135
expr.as_ref().to_string(),
125136
if *negated { "NOT " } else { "" },
126-
list.iter()
127-
.map(|a| a.to_string())
128-
.collect::<Vec<String>>()
129-
.join(", ")
137+
comma_separated_string(list)
130138
),
131139
ASTNode::SQLInSubquery {
132140
expr,
@@ -166,14 +174,13 @@ impl ToString for ASTNode {
166174
format!("{} {}", operator.to_string(), expr.as_ref().to_string())
167175
}
168176
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+
}
177184
ASTNode::SQLCase {
178185
conditions,
179186
results,
@@ -198,6 +205,116 @@ impl ToString for ASTNode {
198205
}
199206
}
200207

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+
201318
/// A top-level statement (SELECT, INSERT, CREATE, etc.)
202319
#[derive(Debug, Clone, PartialEq)]
203320
pub enum SQLStatement {
@@ -279,11 +396,7 @@ impl ToString for SQLStatement {
279396
" VALUES({})",
280397
values
281398
.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))
287400
.collect::<Vec<String>>()
288401
.join(", ")
289402
);
@@ -296,15 +409,8 @@ impl ToString for SQLStatement {
296409
values,
297410
} => {
298411
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));
308414
}
309415
s += " FROM stdin; ";
310416
if !values.is_empty() {
@@ -326,15 +432,8 @@ impl ToString for SQLStatement {
326432
selection,
327433
} => {
328434
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);
338437
}
339438
if let Some(selection) = selection {
340439
s += &format!(" WHERE {}", selection.to_string());
@@ -373,22 +472,14 @@ impl ToString for SQLStatement {
373472
} if *external => format!(
374473
"CREATE EXTERNAL TABLE {} ({}) STORED AS {} LOCATION '{}'",
375474
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(),
382477
location.as_ref().unwrap()
383478
),
384479
SQLStatement::SQLCreateTable { name, columns, .. } => format!(
385480
"CREATE TABLE {} ({})",
386481
name.to_string(),
387-
columns
388-
.iter()
389-
.map(|c| c.to_string())
390-
.collect::<Vec<String>>()
391-
.join(", ")
482+
comma_separated_string(columns)
392483
),
393484
SQLStatement::SQLAlterTable { name, operation } => {
394485
format!("ALTER TABLE {} {}", name.to_string(), operation.to_string())

src/sqlast/query.rs

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,7 @@ impl ToString for SQLQuery {
2929
}
3030
s += &self.body.to_string();
3131
if let Some(ref order_by) = self.order_by {
32-
s += &format!(
33-
" ORDER BY {}",
34-
order_by
35-
.iter()
36-
.map(|o| o.to_string())
37-
.collect::<Vec<String>>()
38-
.join(", ")
39-
);
32+
s += &format!(" ORDER BY {}", comma_separated_string(order_by));
4033
}
4134
if let Some(ref limit) = self.limit {
4235
s += &format!(" LIMIT {}", limit.to_string());
@@ -130,11 +123,7 @@ impl ToString for SQLSelect {
130123
let mut s = format!(
131124
"SELECT{} {}",
132125
if self.distinct { " DISTINCT" } else { "" },
133-
self.projection
134-
.iter()
135-
.map(|p| p.to_string())
136-
.collect::<Vec<String>>()
137-
.join(", ")
126+
comma_separated_string(&self.projection)
138127
);
139128
if let Some(ref relation) = self.relation {
140129
s += &format!(" FROM {}", relation.to_string());
@@ -146,14 +135,7 @@ impl ToString for SQLSelect {
146135
s += &format!(" WHERE {}", selection.to_string());
147136
}
148137
if let Some(ref group_by) = self.group_by {
149-
s += &format!(
150-
" GROUP BY {}",
151-
group_by
152-
.iter()
153-
.map(|g| g.to_string())
154-
.collect::<Vec<String>>()
155-
.join(", ")
156-
);
138+
s += &format!(" GROUP BY {}", comma_separated_string(group_by));
157139
}
158140
if let Some(ref having) = self.having {
159141
s += &format!(" HAVING {}", having.to_string());
@@ -175,7 +157,7 @@ pub enum SQLSelectItem {
175157
/// Any expression, not followed by `[ AS ] alias`
176158
UnnamedExpression(ASTNode),
177159
/// An expression, followed by `[ AS ] alias`
178-
ExpressionWithAlias(ASTNode, SQLIdent),
160+
ExpressionWithAlias { expr: ASTNode, alias: SQLIdent },
179161
/// `alias.*` or even `schema.table.*`
180162
QualifiedWildcard(SQLObjectName),
181163
/// An unqualified `*`
@@ -186,7 +168,7 @@ impl ToString for SQLSelectItem {
186168
fn to_string(&self) -> String {
187169
match &self {
188170
SQLSelectItem::UnnamedExpression(expr) => expr.to_string(),
189-
SQLSelectItem::ExpressionWithAlias(expr, alias) => {
171+
SQLSelectItem::ExpressionWithAlias { expr, alias } => {
190172
format!("{} AS {}", expr.to_string(), alias)
191173
}
192174
SQLSelectItem::QualifiedWildcard(prefix) => format!("{}.*", prefix.to_string()),

0 commit comments

Comments
 (0)