BigQuery: Support window clause using named window (#1237)

This commit is contained in:
Ifeanyi Ubah 2024-04-30 15:41:24 +02:00 committed by GitHub
parent 0b5722afbf
commit 6fcf8c9abe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 120 additions and 11 deletions

View file

@ -44,8 +44,8 @@ pub use self::query::{
ExceptSelectItem, ExcludeSelectItem, Fetch, ForClause, ForJson, ForXml, GroupByExpr, ExceptSelectItem, ExcludeSelectItem, Fetch, ForClause, ForJson, ForXml, GroupByExpr,
IdentWithAlias, IlikeSelectItem, Join, JoinConstraint, JoinOperator, JsonTableColumn, IdentWithAlias, IlikeSelectItem, Join, JoinConstraint, JoinOperator, JsonTableColumn,
JsonTableColumnErrorHandling, LateralView, LockClause, LockType, MatchRecognizePattern, JsonTableColumnErrorHandling, LateralView, LockClause, LockType, MatchRecognizePattern,
MatchRecognizeSymbol, Measure, NamedWindowDefinition, NonBlock, Offset, OffsetRows, MatchRecognizeSymbol, Measure, NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset,
OrderByExpr, Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement, OffsetRows, OrderByExpr, Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement,
ReplaceSelectItem, RowsPerMatch, Select, SelectInto, SelectItem, SetExpr, SetOperator, ReplaceSelectItem, RowsPerMatch, Select, SelectInto, SelectItem, SetExpr, SetOperator,
SetQuantifier, SymbolDefinition, Table, TableAlias, TableFactor, TableVersion, TableWithJoins, SetQuantifier, SymbolDefinition, Table, TableAlias, TableFactor, TableVersion, TableWithJoins,
Top, TopQuantity, ValueTableMode, Values, WildcardAdditionalOptions, With, Top, TopQuantity, ValueTableMode, Values, WildcardAdditionalOptions, With,

View file

@ -358,14 +358,56 @@ impl fmt::Display for LateralView {
} }
} }
/// An expression used in a named window declaration.
///
/// ```sql
/// WINDOW mywindow AS [named_window_expr]
/// ```
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct NamedWindowDefinition(pub Ident, pub WindowSpec); pub enum NamedWindowExpr {
/// A direct reference to another named window definition.
/// [BigQuery]
///
/// Example:
/// ```sql
/// WINDOW mywindow AS prev_window
/// ```
///
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/window-function-calls#ref_named_window
NamedWindow(Ident),
/// A window expression.
///
/// Example:
/// ```sql
/// WINDOW mywindow AS (ORDER BY 1)
/// ```
WindowSpec(WindowSpec),
}
impl fmt::Display for NamedWindowExpr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
NamedWindowExpr::NamedWindow(named_window) => {
write!(f, "{named_window}")?;
}
NamedWindowExpr::WindowSpec(window_spec) => {
write!(f, "({window_spec})")?;
}
};
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct NamedWindowDefinition(pub Ident, pub NamedWindowExpr);
impl fmt::Display for NamedWindowDefinition { impl fmt::Display for NamedWindowDefinition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} AS ({})", self.0, self.1) write!(f, "{} AS {}", self.0, self.1)
} }
} }

View file

@ -34,4 +34,9 @@ impl Dialect for BigQueryDialect {
fn supports_string_literal_backslash_escape(&self) -> bool { fn supports_string_literal_backslash_escape(&self) -> bool {
true true
} }
/// See [doc](https://cloud.google.com/bigquery/docs/reference/standard-sql/window-function-calls#ref_named_window)
fn supports_window_clause_named_window_reference(&self) -> bool {
true
}
} }

View file

@ -54,4 +54,8 @@ impl Dialect for GenericDialect {
fn supports_dictionary_syntax(&self) -> bool { fn supports_dictionary_syntax(&self) -> bool {
true true
} }
fn supports_window_clause_named_window_reference(&self) -> bool {
true
}
} }

View file

@ -143,6 +143,17 @@ pub trait Dialect: Debug + Any {
fn supports_filter_during_aggregation(&self) -> bool { fn supports_filter_during_aggregation(&self) -> bool {
false false
} }
/// Returns true if the dialect supports referencing another named window
/// within a window clause declaration.
///
/// Example
/// ```sql
/// SELECT * FROM mytable
/// WINDOW mynamed_window AS another_named_window
/// ```
fn supports_window_clause_named_window_reference(&self) -> bool {
false
}
/// Returns true if the dialect supports `ARRAY_AGG() [WITHIN GROUP (ORDER BY)]` expressions. /// Returns true if the dialect supports `ARRAY_AGG() [WITHIN GROUP (ORDER BY)]` expressions.
/// Otherwise, the dialect should expect an `ORDER BY` without the `WITHIN GROUP` clause, e.g. [`ANSI`] /// Otherwise, the dialect should expect an `ORDER BY` without the `WITHIN GROUP` clause, e.g. [`ANSI`]
/// ///

View file

@ -10121,9 +10121,16 @@ impl<'a> Parser<'a> {
pub fn parse_named_window(&mut self) -> Result<NamedWindowDefinition, ParserError> { pub fn parse_named_window(&mut self) -> Result<NamedWindowDefinition, ParserError> {
let ident = self.parse_identifier(false)?; let ident = self.parse_identifier(false)?;
self.expect_keyword(Keyword::AS)?; self.expect_keyword(Keyword::AS)?;
self.expect_token(&Token::LParen)?;
let window_spec = self.parse_window_spec()?; let window_expr = if self.consume_token(&Token::LParen) {
Ok(NamedWindowDefinition(ident, window_spec)) NamedWindowExpr::WindowSpec(self.parse_window_spec()?)
} else if self.dialect.supports_window_clause_named_window_reference() {
NamedWindowExpr::NamedWindow(self.parse_identifier(false)?)
} else {
return self.expected("(", self.peek_token());
};
Ok(NamedWindowDefinition(ident, window_expr))
} }
pub fn parse_create_procedure(&mut self, or_alter: bool) -> Result<Statement, ParserError> { pub fn parse_create_procedure(&mut self, or_alter: bool) -> Result<Statement, ParserError> {

View file

@ -4341,6 +4341,31 @@ fn parse_named_window_functions() {
supported_dialects.verified_stmt(sql); supported_dialects.verified_stmt(sql);
} }
#[test]
fn parse_window_clause() {
let sql = "SELECT * \
FROM mytable \
WINDOW \
window1 AS (ORDER BY 1 ASC, 2 DESC, 3 NULLS FIRST), \
window2 AS (window1), \
window3 AS (PARTITION BY a, b, c), \
window4 AS (ROWS UNBOUNDED PRECEDING), \
window5 AS (window1 PARTITION BY a), \
window6 AS (window1 ORDER BY a), \
window7 AS (window1 ROWS UNBOUNDED PRECEDING), \
window8 AS (window1 PARTITION BY a ORDER BY b ROWS UNBOUNDED PRECEDING) \
ORDER BY C3";
verified_only_select(sql);
let sql = "SELECT from mytable WINDOW window1 AS window2";
let dialects = all_dialects_except(|d| d.is::<BigQueryDialect>() || d.is::<GenericDialect>());
let res = dialects.parse_sql_statements(sql);
assert_eq!(
ParserError::ParserError("Expected (, found: window2".to_string()),
res.unwrap_err()
);
}
#[test] #[test]
fn test_parse_named_window() { fn test_parse_named_window() {
let sql = "SELECT \ let sql = "SELECT \
@ -4438,7 +4463,7 @@ fn test_parse_named_window() {
value: "window1".to_string(), value: "window1".to_string(),
quote_style: None, quote_style: None,
}, },
WindowSpec { NamedWindowExpr::WindowSpec(WindowSpec {
window_name: None, window_name: None,
partition_by: vec![], partition_by: vec![],
order_by: vec![OrderByExpr { order_by: vec![OrderByExpr {
@ -4450,14 +4475,14 @@ fn test_parse_named_window() {
nulls_first: None, nulls_first: None,
}], }],
window_frame: None, window_frame: None,
}, }),
), ),
NamedWindowDefinition( NamedWindowDefinition(
Ident { Ident {
value: "window2".to_string(), value: "window2".to_string(),
quote_style: None, quote_style: None,
}, },
WindowSpec { NamedWindowExpr::WindowSpec(WindowSpec {
window_name: None, window_name: None,
partition_by: vec![Expr::Identifier(Ident { partition_by: vec![Expr::Identifier(Ident {
value: "C11".to_string(), value: "C11".to_string(),
@ -4465,7 +4490,7 @@ fn test_parse_named_window() {
})], })],
order_by: vec![], order_by: vec![],
window_frame: None, window_frame: None,
}, }),
), ),
], ],
qualify: None, qualify: None,
@ -4475,6 +4500,21 @@ fn test_parse_named_window() {
assert_eq!(actual_select_only, expected); assert_eq!(actual_select_only, expected);
} }
#[test]
fn parse_window_clause_named_window() {
let sql = "SELECT * FROM mytable WINDOW window1 AS window2";
let Select { named_window, .. } =
all_dialects_where(|d| d.supports_window_clause_named_window_reference())
.verified_only_select(sql);
assert_eq!(
vec![NamedWindowDefinition(
Ident::new("window1"),
NamedWindowExpr::NamedWindow(Ident::new("window2"))
)],
named_window
);
}
#[test] #[test]
fn parse_aggregate_with_group_by() { fn parse_aggregate_with_group_by() {
let sql = "SELECT a, COUNT(1), MIN(b), MAX(b) FROM foo GROUP BY a"; let sql = "SELECT a, COUNT(1), MIN(b), MAX(b) FROM foo GROUP BY a";