Skip to content

Commit 80e4c78

Browse files
iffyioJichaoS
authored andcommitted
BigQuery: Support window clause using named window (apache#1237)
1 parent 9c0ffc0 commit 80e4c78

File tree

7 files changed

+120
-11
lines changed

7 files changed

+120
-11
lines changed

src/ast/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ pub use self::query::{
4444
ExceptSelectItem, ExcludeSelectItem, Fetch, ForClause, ForJson, ForXml, GroupByExpr,
4545
IdentWithAlias, IlikeSelectItem, Join, JoinConstraint, JoinOperator, JsonTableColumn,
4646
JsonTableColumnErrorHandling, LateralView, LockClause, LockType, MatchRecognizePattern,
47-
MatchRecognizeSymbol, Measure, NamedWindowDefinition, NonBlock, Offset, OffsetRows,
48-
OrderByExpr, Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement,
47+
MatchRecognizeSymbol, Measure, NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset,
48+
OffsetRows, OrderByExpr, Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement,
4949
ReplaceSelectItem, RowsPerMatch, Select, SelectInto, SelectItem, SetExpr, SetOperator,
5050
SetQuantifier, SymbolDefinition, Table, TableAlias, TableFactor, TableVersion, TableWithJoins,
5151
Top, TopQuantity, ValueTableMode, Values, WildcardAdditionalOptions, With,

src/ast/query.rs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,14 +358,56 @@ impl fmt::Display for LateralView {
358358
}
359359
}
360360

361+
/// An expression used in a named window declaration.
362+
///
363+
/// ```sql
364+
/// WINDOW mywindow AS [named_window_expr]
365+
/// ```
366+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
367+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
368+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
369+
pub enum NamedWindowExpr {
370+
/// A direct reference to another named window definition.
371+
/// [BigQuery]
372+
///
373+
/// Example:
374+
/// ```sql
375+
/// WINDOW mywindow AS prev_window
376+
/// ```
377+
///
378+
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/window-function-calls#ref_named_window
379+
NamedWindow(Ident),
380+
/// A window expression.
381+
///
382+
/// Example:
383+
/// ```sql
384+
/// WINDOW mywindow AS (ORDER BY 1)
385+
/// ```
386+
WindowSpec(WindowSpec),
387+
}
388+
389+
impl fmt::Display for NamedWindowExpr {
390+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
391+
match self {
392+
NamedWindowExpr::NamedWindow(named_window) => {
393+
write!(f, "{named_window}")?;
394+
}
395+
NamedWindowExpr::WindowSpec(window_spec) => {
396+
write!(f, "({window_spec})")?;
397+
}
398+
};
399+
Ok(())
400+
}
401+
}
402+
361403
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
362404
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
363405
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
364-
pub struct NamedWindowDefinition(pub Ident, pub WindowSpec);
406+
pub struct NamedWindowDefinition(pub Ident, pub NamedWindowExpr);
365407

366408
impl fmt::Display for NamedWindowDefinition {
367409
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
368-
write!(f, "{} AS ({})", self.0, self.1)
410+
write!(f, "{} AS {}", self.0, self.1)
369411
}
370412
}
371413

src/dialect/bigquery.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,9 @@ impl Dialect for BigQueryDialect {
3434
fn supports_string_literal_backslash_escape(&self) -> bool {
3535
true
3636
}
37+
38+
/// See [doc](https://cloud.google.com/bigquery/docs/reference/standard-sql/window-function-calls#ref_named_window)
39+
fn supports_window_clause_named_window_reference(&self) -> bool {
40+
true
41+
}
3742
}

src/dialect/generic.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,8 @@ impl Dialect for GenericDialect {
5454
fn supports_dictionary_syntax(&self) -> bool {
5555
true
5656
}
57+
58+
fn supports_window_clause_named_window_reference(&self) -> bool {
59+
true
60+
}
5761
}

src/dialect/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,17 @@ pub trait Dialect: Debug + Any {
143143
fn supports_filter_during_aggregation(&self) -> bool {
144144
false
145145
}
146+
/// Returns true if the dialect supports referencing another named window
147+
/// within a window clause declaration.
148+
///
149+
/// Example
150+
/// ```sql
151+
/// SELECT * FROM mytable
152+
/// WINDOW mynamed_window AS another_named_window
153+
/// ```
154+
fn supports_window_clause_named_window_reference(&self) -> bool {
155+
false
156+
}
146157
/// Returns true if the dialect supports `ARRAY_AGG() [WITHIN GROUP (ORDER BY)]` expressions.
147158
/// Otherwise, the dialect should expect an `ORDER BY` without the `WITHIN GROUP` clause, e.g. [`ANSI`]
148159
///

src/parser/mod.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10121,9 +10121,16 @@ impl<'a> Parser<'a> {
1012110121
pub fn parse_named_window(&mut self) -> Result<NamedWindowDefinition, ParserError> {
1012210122
let ident = self.parse_identifier(false)?;
1012310123
self.expect_keyword(Keyword::AS)?;
10124-
self.expect_token(&Token::LParen)?;
10125-
let window_spec = self.parse_window_spec()?;
10126-
Ok(NamedWindowDefinition(ident, window_spec))
10124+
10125+
let window_expr = if self.consume_token(&Token::LParen) {
10126+
NamedWindowExpr::WindowSpec(self.parse_window_spec()?)
10127+
} else if self.dialect.supports_window_clause_named_window_reference() {
10128+
NamedWindowExpr::NamedWindow(self.parse_identifier(false)?)
10129+
} else {
10130+
return self.expected("(", self.peek_token());
10131+
};
10132+
10133+
Ok(NamedWindowDefinition(ident, window_expr))
1012710134
}
1012810135

1012910136
pub fn parse_create_procedure(&mut self, or_alter: bool) -> Result<Statement, ParserError> {

tests/sqlparser_common.rs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4341,6 +4341,31 @@ fn parse_named_window_functions() {
43414341
supported_dialects.verified_stmt(sql);
43424342
}
43434343

4344+
#[test]
4345+
fn parse_window_clause() {
4346+
let sql = "SELECT * \
4347+
FROM mytable \
4348+
WINDOW \
4349+
window1 AS (ORDER BY 1 ASC, 2 DESC, 3 NULLS FIRST), \
4350+
window2 AS (window1), \
4351+
window3 AS (PARTITION BY a, b, c), \
4352+
window4 AS (ROWS UNBOUNDED PRECEDING), \
4353+
window5 AS (window1 PARTITION BY a), \
4354+
window6 AS (window1 ORDER BY a), \
4355+
window7 AS (window1 ROWS UNBOUNDED PRECEDING), \
4356+
window8 AS (window1 PARTITION BY a ORDER BY b ROWS UNBOUNDED PRECEDING) \
4357+
ORDER BY C3";
4358+
verified_only_select(sql);
4359+
4360+
let sql = "SELECT from mytable WINDOW window1 AS window2";
4361+
let dialects = all_dialects_except(|d| d.is::<BigQueryDialect>() || d.is::<GenericDialect>());
4362+
let res = dialects.parse_sql_statements(sql);
4363+
assert_eq!(
4364+
ParserError::ParserError("Expected (, found: window2".to_string()),
4365+
res.unwrap_err()
4366+
);
4367+
}
4368+
43444369
#[test]
43454370
fn test_parse_named_window() {
43464371
let sql = "SELECT \
@@ -4438,7 +4463,7 @@ fn test_parse_named_window() {
44384463
value: "window1".to_string(),
44394464
quote_style: None,
44404465
},
4441-
WindowSpec {
4466+
NamedWindowExpr::WindowSpec(WindowSpec {
44424467
window_name: None,
44434468
partition_by: vec![],
44444469
order_by: vec![OrderByExpr {
@@ -4450,22 +4475,22 @@ fn test_parse_named_window() {
44504475
nulls_first: None,
44514476
}],
44524477
window_frame: None,
4453-
},
4478+
}),
44544479
),
44554480
NamedWindowDefinition(
44564481
Ident {
44574482
value: "window2".to_string(),
44584483
quote_style: None,
44594484
},
4460-
WindowSpec {
4485+
NamedWindowExpr::WindowSpec(WindowSpec {
44614486
window_name: None,
44624487
partition_by: vec![Expr::Identifier(Ident {
44634488
value: "C11".to_string(),
44644489
quote_style: None,
44654490
})],
44664491
order_by: vec![],
44674492
window_frame: None,
4468-
},
4493+
}),
44694494
),
44704495
],
44714496
qualify: None,
@@ -4475,6 +4500,21 @@ fn test_parse_named_window() {
44754500
assert_eq!(actual_select_only, expected);
44764501
}
44774502

4503+
#[test]
4504+
fn parse_window_clause_named_window() {
4505+
let sql = "SELECT * FROM mytable WINDOW window1 AS window2";
4506+
let Select { named_window, .. } =
4507+
all_dialects_where(|d| d.supports_window_clause_named_window_reference())
4508+
.verified_only_select(sql);
4509+
assert_eq!(
4510+
vec![NamedWindowDefinition(
4511+
Ident::new("window1"),
4512+
NamedWindowExpr::NamedWindow(Ident::new("window2"))
4513+
)],
4514+
named_window
4515+
);
4516+
}
4517+
44784518
#[test]
44794519
fn parse_aggregate_with_group_by() {
44804520
let sql = "SELECT a, COUNT(1), MIN(b), MAX(b) FROM foo GROUP BY a";

0 commit comments

Comments
 (0)