Skip to content

Commit 2310330

Browse files
Nikita-stralamb
andauthored
Support named windows in OVER (window_definition) clause (apache#1166)
Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
1 parent 83c5d81 commit 2310330

4 files changed

Lines changed: 92 additions & 2 deletions

File tree

src/ast/mod.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1246,11 +1246,19 @@ impl Display for WindowType {
12461246
}
12471247
}
12481248

1249-
/// A window specification (i.e. `OVER (PARTITION BY .. ORDER BY .. etc.)`)
1249+
/// A window specification (i.e. `OVER ([window_name] PARTITION BY .. ORDER BY .. etc.)`)
12501250
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
12511251
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12521252
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
12531253
pub struct WindowSpec {
1254+
/// Optional window name.
1255+
///
1256+
/// You can find it at least in [MySQL][1], [BigQuery][2], [PostgreSQL][3]
1257+
///
1258+
/// [1]: https://dev.mysql.com/doc/refman/8.0/en/window-functions-named-windows.html
1259+
/// [2]: https://cloud.google.com/bigquery/docs/reference/standard-sql/window-function-calls
1260+
/// [3]: https://www.postgresql.org/docs/current/sql-expressions.html#SYNTAX-WINDOW-FUNCTIONS
1261+
pub window_name: Option<Ident>,
12541262
/// `OVER (PARTITION BY ...)`
12551263
pub partition_by: Vec<Expr>,
12561264
/// `OVER (ORDER BY ...)`
@@ -1262,7 +1270,12 @@ pub struct WindowSpec {
12621270
impl fmt::Display for WindowSpec {
12631271
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
12641272
let mut delim = "";
1273+
if let Some(window_name) = &self.window_name {
1274+
delim = " ";
1275+
write!(f, "{window_name}")?;
1276+
}
12651277
if !self.partition_by.is_empty() {
1278+
f.write_str(delim)?;
12661279
delim = " ";
12671280
write!(
12681281
f,

src/parser/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9528,6 +9528,13 @@ impl<'a> Parser<'a> {
95289528
}
95299529

95309530
pub fn parse_window_spec(&mut self) -> Result<WindowSpec, ParserError> {
9531+
let window_name = match self.peek_token().token {
9532+
Token::Word(word) if word.keyword == Keyword::NoKeyword => {
9533+
self.maybe_parse(|parser| parser.parse_identifier(false))
9534+
}
9535+
_ => None,
9536+
};
9537+
95319538
let partition_by = if self.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
95329539
self.parse_comma_separated(Parser::parse_expr)?
95339540
} else {
@@ -9538,6 +9545,7 @@ impl<'a> Parser<'a> {
95389545
} else {
95399546
vec![]
95409547
};
9548+
95419549
let window_frame = if !self.consume_token(&Token::RParen) {
95429550
let window_frame = self.parse_window_frame()?;
95439551
self.expect_token(&Token::RParen)?;
@@ -9546,6 +9554,7 @@ impl<'a> Parser<'a> {
95469554
None
95479555
};
95489556
Ok(WindowSpec {
9557+
window_name,
95499558
partition_by,
95509559
order_by,
95519560
window_frame,

tests/sqlparser_common.rs

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2062,6 +2062,7 @@ fn parse_select_qualify() {
20622062
null_treatment: None,
20632063
filter: None,
20642064
over: Some(WindowType::WindowSpec(WindowSpec {
2065+
window_name: None,
20652066
partition_by: vec![Expr::Identifier(Ident::new("p"))],
20662067
order_by: vec![OrderByExpr {
20672068
expr: Expr::Identifier(Ident::new("o")),
@@ -4122,14 +4123,18 @@ fn parse_window_functions() {
41224123
GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) \
41234124
FROM foo";
41244125
let select = verified_only_select(sql);
4125-
assert_eq!(7, select.projection.len());
4126+
4127+
const EXPECTED_PROJ_QTY: usize = 7;
4128+
assert_eq!(EXPECTED_PROJ_QTY, select.projection.len());
4129+
41264130
assert_eq!(
41274131
&Expr::Function(Function {
41284132
name: ObjectName(vec![Ident::new("row_number")]),
41294133
args: vec![],
41304134
null_treatment: None,
41314135
filter: None,
41324136
over: Some(WindowType::WindowSpec(WindowSpec {
4137+
window_name: None,
41334138
partition_by: vec![],
41344139
order_by: vec![OrderByExpr {
41354140
expr: Expr::Identifier(Ident::new("dt")),
@@ -4144,6 +4149,66 @@ fn parse_window_functions() {
41444149
}),
41454150
expr_from_projection(&select.projection[0])
41464151
);
4152+
4153+
for i in 0..EXPECTED_PROJ_QTY {
4154+
assert!(matches!(
4155+
expr_from_projection(&select.projection[i]),
4156+
Expr::Function(Function {
4157+
over: Some(WindowType::WindowSpec(WindowSpec {
4158+
window_name: None,
4159+
..
4160+
})),
4161+
..
4162+
})
4163+
));
4164+
}
4165+
}
4166+
4167+
#[test]
4168+
fn parse_named_window_functions() {
4169+
let supported_dialects = TestedDialects {
4170+
dialects: vec![
4171+
Box::new(GenericDialect {}),
4172+
Box::new(PostgreSqlDialect {}),
4173+
Box::new(MySqlDialect {}),
4174+
Box::new(BigQueryDialect {}),
4175+
],
4176+
options: None,
4177+
};
4178+
4179+
let sql = "SELECT row_number() OVER (w ORDER BY dt DESC), \
4180+
sum(foo) OVER (win PARTITION BY a, b ORDER BY c, d \
4181+
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) \
4182+
FROM foo \
4183+
WINDOW w AS (PARTITION BY x), win AS (ORDER BY y)";
4184+
supported_dialects.verified_stmt(sql);
4185+
4186+
let select = verified_only_select(sql);
4187+
4188+
const EXPECTED_PROJ_QTY: usize = 2;
4189+
assert_eq!(EXPECTED_PROJ_QTY, select.projection.len());
4190+
4191+
const EXPECTED_WIN_NAMES: [&str; 2] = ["w", "win"];
4192+
for (i, win_name) in EXPECTED_WIN_NAMES.iter().enumerate() {
4193+
assert!(matches!(
4194+
expr_from_projection(&select.projection[i]),
4195+
Expr::Function(Function {
4196+
over: Some(WindowType::WindowSpec(WindowSpec {
4197+
window_name: Some(Ident { value, .. }),
4198+
..
4199+
})),
4200+
..
4201+
}) if value == win_name
4202+
));
4203+
}
4204+
4205+
let sql = "SELECT \
4206+
FIRST_VALUE(x) OVER (w ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS first, \
4207+
FIRST_VALUE(x) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS last, \
4208+
SUM(y) OVER (win PARTITION BY x) AS last \
4209+
FROM EMPLOYEE \
4210+
WINDOW w AS (PARTITION BY x), win AS (w ORDER BY y)";
4211+
supported_dialects.verified_stmt(sql);
41474212
}
41484213

41494214
#[test]
@@ -4244,6 +4309,7 @@ fn test_parse_named_window() {
42444309
quote_style: None,
42454310
},
42464311
WindowSpec {
4312+
window_name: None,
42474313
partition_by: vec![],
42484314
order_by: vec![OrderByExpr {
42494315
expr: Expr::Identifier(Ident {
@@ -4262,6 +4328,7 @@ fn test_parse_named_window() {
42624328
quote_style: None,
42634329
},
42644330
WindowSpec {
4331+
window_name: None,
42654332
partition_by: vec![Expr::Identifier(Ident {
42664333
value: "C11".to_string(),
42674334
quote_style: None,

tests/sqlparser_sqlite.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ fn parse_window_function_with_filter() {
446446
))],
447447
null_treatment: None,
448448
over: Some(WindowType::WindowSpec(WindowSpec {
449+
window_name: None,
449450
partition_by: vec![],
450451
order_by: vec![],
451452
window_frame: None,

0 commit comments

Comments
 (0)