diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 017c45430..27d4ad7f9 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -44,8 +44,8 @@ pub use self::query::{ ExcludeSelectItem, Fetch, ForClause, ForJson, ForXml, GroupByExpr, IdentWithAlias, IlikeSelectItem, Join, JoinConstraint, JoinOperator, JsonTableColumn, JsonTableColumnErrorHandling, LateralView, LockClause, LockType, MatchRecognizePattern, - MatchRecognizeSymbol, Measure, NamedWindowDefinition, NonBlock, Offset, OffsetRows, - OrderByExpr, Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement, + MatchRecognizeSymbol, Measure, NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, + OffsetRows, OrderByExpr, Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, SymbolDefinition, Table, TableAlias, TableFactor, TableVersion, TableWithJoins, Top, TopQuantity, ValueTableMode, Values, WildcardAdditionalOptions, With, diff --git a/src/ast/query.rs b/src/ast/query.rs index 5f5bca4cc..c4016590a 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -353,14 +353,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)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +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 WindowSpec); +pub struct NamedWindowDefinition(pub Ident, pub NamedWindowExpr); impl fmt::Display for NamedWindowDefinition { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} AS ({})", self.0, self.1) + write!(f, "{} AS {}", self.0, self.1) } } diff --git a/src/dialect/bigquery.rs b/src/dialect/bigquery.rs index d36910dbc..7475b6a53 100644 --- a/src/dialect/bigquery.rs +++ b/src/dialect/bigquery.rs @@ -34,4 +34,9 @@ impl Dialect for BigQueryDialect { fn supports_string_literal_backslash_escape(&self) -> bool { 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 + } } diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index 17383b526..90f784aa1 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -50,4 +50,8 @@ impl Dialect for GenericDialect { fn supports_dictionary_syntax(&self) -> bool { true } + + fn supports_window_clause_named_window_reference(&self) -> bool { + true + } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index a351605aa..b892c368c 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -143,6 +143,17 @@ pub trait Dialect: Debug + Any { fn supports_filter_during_aggregation(&self) -> bool { 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. /// Otherwise, the dialect should expect an `ORDER BY` without the `WITHIN GROUP` clause, e.g. [`ANSI`] /// diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 8f6dace7a..8c6c8b636 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -10055,9 +10055,16 @@ impl<'a> Parser<'a> { pub fn parse_named_window(&mut self) -> Result { let ident = self.parse_identifier(false)?; self.expect_keyword(Keyword::AS)?; - self.expect_token(&Token::LParen)?; - let window_spec = self.parse_window_spec()?; - Ok(NamedWindowDefinition(ident, window_spec)) + + let window_expr = if self.consume_token(&Token::LParen) { + 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 { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 8f48f0f48..f4f42385e 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -4340,6 +4340,31 @@ fn parse_named_window_functions() { 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::() || d.is::()); + let res = dialects.parse_sql_statements(sql); + assert_eq!( + ParserError::ParserError("Expected (, found: window2".to_string()), + res.unwrap_err() + ); +} + #[test] fn test_parse_named_window() { let sql = "SELECT \ @@ -4437,7 +4462,7 @@ fn test_parse_named_window() { value: "window1".to_string(), quote_style: None, }, - WindowSpec { + NamedWindowExpr::WindowSpec(WindowSpec { window_name: None, partition_by: vec![], order_by: vec![OrderByExpr { @@ -4449,14 +4474,14 @@ fn test_parse_named_window() { nulls_first: None, }], window_frame: None, - }, + }), ), NamedWindowDefinition( Ident { value: "window2".to_string(), quote_style: None, }, - WindowSpec { + NamedWindowExpr::WindowSpec(WindowSpec { window_name: None, partition_by: vec![Expr::Identifier(Ident { value: "C11".to_string(), @@ -4464,7 +4489,7 @@ fn test_parse_named_window() { })], order_by: vec![], window_frame: None, - }, + }), ), ], qualify: None, @@ -4473,6 +4498,21 @@ fn test_parse_named_window() { 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] fn parse_aggregate_with_group_by() { let sql = "SELECT a, COUNT(1), MIN(b), MAX(b) FROM foo GROUP BY a";