Skip to content

Commit f1216fc

Browse files
committed
BigQuery: Support for window clause named window
BigQuery allows window clause declaration using either a window spec or a reference to another named window. This adds support for the latter. https://cloud.google.com/bigquery/docs/reference/standard-sql/window-function-calls#ref_named_window
1 parent deaa6d8 commit f1216fc

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
ExcludeSelectItem, Fetch, ForClause, ForJson, ForXml, GroupByExpr, IdentWithAlias,
4545
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
@@ -353,14 +353,56 @@ impl fmt::Display for LateralView {
353353
}
354354
}
355355

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

361403
impl fmt::Display for NamedWindowDefinition {
362404
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
363-
write!(f, "{} AS ({})", self.0, self.1)
405+
write!(f, "{} AS {}", self.0, self.1)
364406
}
365407
}
366408

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
@@ -50,4 +50,8 @@ impl Dialect for GenericDialect {
5050
fn supports_dictionary_syntax(&self) -> bool {
5151
true
5252
}
53+
54+
fn supports_window_clause_named_window_reference(&self) -> bool {
55+
true
56+
}
5357
}

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
@@ -10055,9 +10055,16 @@ impl<'a> Parser<'a> {
1005510055
pub fn parse_named_window(&mut self) -> Result<NamedWindowDefinition, ParserError> {
1005610056
let ident = self.parse_identifier(false)?;
1005710057
self.expect_keyword(Keyword::AS)?;
10058-
self.expect_token(&Token::LParen)?;
10059-
let window_spec = self.parse_window_spec()?;
10060-
Ok(NamedWindowDefinition(ident, window_spec))
10058+
10059+
let window_expr = if self.consume_token(&Token::LParen) {
10060+
NamedWindowExpr::WindowSpec(self.parse_window_spec()?)
10061+
} else if self.dialect.supports_window_clause_named_window_reference() {
10062+
NamedWindowExpr::NamedWindow(self.parse_identifier(false)?)
10063+
} else {
10064+
return self.expected("(", self.peek_token());
10065+
};
10066+
10067+
Ok(NamedWindowDefinition(ident, window_expr))
1006110068
}
1006210069

1006310070
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
@@ -4340,6 +4340,31 @@ fn parse_named_window_functions() {
43404340
supported_dialects.verified_stmt(sql);
43414341
}
43424342

4343+
#[test]
4344+
fn parse_window_clause() {
4345+
let sql = "SELECT * \
4346+
FROM mytable \
4347+
WINDOW \
4348+
window1 AS (ORDER BY 1 ASC, 2 DESC, 3 NULLS FIRST), \
4349+
window2 AS (window1), \
4350+
window3 AS (PARTITION BY a, b, c), \
4351+
window4 AS (ROWS UNBOUNDED PRECEDING), \
4352+
window5 AS (window1 PARTITION BY a), \
4353+
window6 AS (window1 ORDER BY a), \
4354+
window7 AS (window1 ROWS UNBOUNDED PRECEDING), \
4355+
window8 AS (window1 PARTITION BY a ORDER BY b ROWS UNBOUNDED PRECEDING) \
4356+
ORDER BY C3";
4357+
verified_only_select(sql);
4358+
4359+
let sql = "SELECT from mytable WINDOW window1 AS window2";
4360+
let dialects = all_dialects_except(|d| d.is::<BigQueryDialect>() || d.is::<GenericDialect>());
4361+
let res = dialects.parse_sql_statements(sql);
4362+
assert_eq!(
4363+
ParserError::ParserError("Expected (, found: window2".to_string()),
4364+
res.unwrap_err()
4365+
);
4366+
}
4367+
43434368
#[test]
43444369
fn test_parse_named_window() {
43454370
let sql = "SELECT \
@@ -4437,7 +4462,7 @@ fn test_parse_named_window() {
44374462
value: "window1".to_string(),
44384463
quote_style: None,
44394464
},
4440-
WindowSpec {
4465+
NamedWindowExpr::WindowSpec(WindowSpec {
44414466
window_name: None,
44424467
partition_by: vec![],
44434468
order_by: vec![OrderByExpr {
@@ -4449,22 +4474,22 @@ fn test_parse_named_window() {
44494474
nulls_first: None,
44504475
}],
44514476
window_frame: None,
4452-
},
4477+
}),
44534478
),
44544479
NamedWindowDefinition(
44554480
Ident {
44564481
value: "window2".to_string(),
44574482
quote_style: None,
44584483
},
4459-
WindowSpec {
4484+
NamedWindowExpr::WindowSpec(WindowSpec {
44604485
window_name: None,
44614486
partition_by: vec![Expr::Identifier(Ident {
44624487
value: "C11".to_string(),
44634488
quote_style: None,
44644489
})],
44654490
order_by: vec![],
44664491
window_frame: None,
4467-
},
4492+
}),
44684493
),
44694494
],
44704495
qualify: None,
@@ -4473,6 +4498,21 @@ fn test_parse_named_window() {
44734498
assert_eq!(actual_select_only, expected);
44744499
}
44754500

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

0 commit comments

Comments
 (0)