Skip to content

Commit ef46cd3

Browse files
berkaysynnadamustafasrepoozankabak
authored
Named window frames (#881)
* after over clause, named window can be parsed with window ... as after having clause * Lint errors are fixed * Support for multiple windows * fix lint errors * simplifications * rename function * Rewrite named window search in functional style * Test added and some minor changes * Minor changes on tests and namings, and semantic check is removed --------- Co-authored-by: Mustafa Akur <[email protected]> Co-authored-by: Mehmet Ozan Kabak <[email protected]>
1 parent 1b86abe commit ef46cd3

8 files changed

+225
-37
lines changed

src/ast/mod.rs

+24-7
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use alloc::{
1717
string::{String, ToString},
1818
vec::Vec,
1919
};
20-
use core::fmt;
20+
use core::fmt::{self, Display};
2121

2222
#[cfg(feature = "serde")]
2323
use serde::{Deserialize, Serialize};
@@ -35,10 +35,10 @@ pub use self::ddl::{
3535
pub use self::operator::{BinaryOperator, UnaryOperator};
3636
pub use self::query::{
3737
Cte, Distinct, ExceptSelectItem, ExcludeSelectItem, Fetch, IdentWithAlias, Join,
38-
JoinConstraint, JoinOperator, LateralView, LockClause, LockType, NonBlock, Offset, OffsetRows,
39-
OrderByExpr, Query, RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select,
40-
SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Table, TableAlias, TableFactor,
41-
TableWithJoins, Top, Values, WildcardAdditionalOptions, With,
38+
JoinConstraint, JoinOperator, LateralView, LockClause, LockType, NamedWindowDefinition,
39+
NonBlock, Offset, OffsetRows, OrderByExpr, Query, RenameSelectItem, ReplaceSelectElement,
40+
ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Table,
41+
TableAlias, TableFactor, TableWithJoins, Top, Values, WildcardAdditionalOptions, With,
4242
};
4343
pub use self::value::{
4444
escape_quoted_string, DateTimeField, DollarQuotedString, TrimWhereField, Value,
@@ -930,6 +930,23 @@ impl fmt::Display for Expr {
930930
}
931931
}
932932

933+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
934+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
935+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
936+
pub enum WindowType {
937+
WindowSpec(WindowSpec),
938+
NamedWindow(Ident),
939+
}
940+
941+
impl Display for WindowType {
942+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
943+
match self {
944+
WindowType::WindowSpec(spec) => write!(f, "({})", spec),
945+
WindowType::NamedWindow(name) => write!(f, "{}", name),
946+
}
947+
}
948+
}
949+
933950
/// A window specification (i.e. `OVER (PARTITION BY .. ORDER BY .. etc.)`)
934951
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
935952
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -3360,7 +3377,7 @@ impl fmt::Display for CloseCursor {
33603377
pub struct Function {
33613378
pub name: ObjectName,
33623379
pub args: Vec<FunctionArg>,
3363-
pub over: Option<WindowSpec>,
3380+
pub over: Option<WindowType>,
33643381
// aggregate functions may specify eg `COUNT(DISTINCT x)`
33653382
pub distinct: bool,
33663383
// Some functions must be called without trailing parentheses, for example Postgres
@@ -3409,7 +3426,7 @@ impl fmt::Display for Function {
34093426
)?;
34103427

34113428
if let Some(o) = &self.over {
3412-
write!(f, " OVER ({o})")?;
3429+
write!(f, " OVER {o}")?;
34133430
}
34143431
}
34153432

src/ast/query.rs

+16
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ pub struct Select {
216216
pub sort_by: Vec<Expr>,
217217
/// HAVING
218218
pub having: Option<Expr>,
219+
/// WINDOW AS
220+
pub named_window: Vec<NamedWindowDefinition>,
219221
/// QUALIFY (Snowflake)
220222
pub qualify: Option<Expr>,
221223
}
@@ -269,6 +271,9 @@ impl fmt::Display for Select {
269271
if let Some(ref having) = self.having {
270272
write!(f, " HAVING {having}")?;
271273
}
274+
if !self.named_window.is_empty() {
275+
write!(f, " WINDOW {}", display_comma_separated(&self.named_window))?;
276+
}
272277
if let Some(ref qualify) = self.qualify {
273278
write!(f, " QUALIFY {qualify}")?;
274279
}
@@ -311,6 +316,17 @@ impl fmt::Display for LateralView {
311316
}
312317
}
313318

319+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
320+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
321+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
322+
pub struct NamedWindowDefinition(pub Ident, pub WindowSpec);
323+
324+
impl fmt::Display for NamedWindowDefinition {
325+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
326+
write!(f, "{} AS ({})", self.0, self.1)
327+
}
328+
}
329+
314330
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
315331
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
316332
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]

src/keywords.rs

+1
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,7 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
688688
Keyword::OUTER,
689689
Keyword::SET,
690690
Keyword::QUALIFY,
691+
Keyword::WINDOW,
691692
];
692693

693694
/// Can't be used as a column alias, so that `SELECT <expr> alias`

src/parser.rs

+45-25
Original file line numberDiff line numberDiff line change
@@ -884,32 +884,12 @@ impl<'a> Parser<'a> {
884884
let distinct = self.parse_all_or_distinct()?.is_some();
885885
let (args, order_by) = self.parse_optional_args_with_orderby()?;
886886
let over = if self.parse_keyword(Keyword::OVER) {
887-
// TBD: support window names (`OVER mywin`) in place of inline specification
888-
self.expect_token(&Token::LParen)?;
889-
let partition_by = if self.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
890-
// a list of possibly-qualified column names
891-
self.parse_comma_separated(Parser::parse_expr)?
892-
} else {
893-
vec![]
894-
};
895-
let order_by = if self.parse_keywords(&[Keyword::ORDER, Keyword::BY]) {
896-
self.parse_comma_separated(Parser::parse_order_by_expr)?
897-
} else {
898-
vec![]
899-
};
900-
let window_frame = if !self.consume_token(&Token::RParen) {
901-
let window_frame = self.parse_window_frame()?;
902-
self.expect_token(&Token::RParen)?;
903-
Some(window_frame)
887+
if self.consume_token(&Token::LParen) {
888+
let window_spec = self.parse_window_spec()?;
889+
Some(WindowType::WindowSpec(window_spec))
904890
} else {
905-
None
906-
};
907-
908-
Some(WindowSpec {
909-
partition_by,
910-
order_by,
911-
window_frame,
912-
})
891+
Some(WindowType::NamedWindow(self.parse_identifier()?))
892+
}
913893
} else {
914894
None
915895
};
@@ -5367,6 +5347,12 @@ impl<'a> Parser<'a> {
53675347
None
53685348
};
53695349

5350+
let named_windows = if self.parse_keyword(Keyword::WINDOW) {
5351+
self.parse_comma_separated(Parser::parse_named_window)?
5352+
} else {
5353+
vec![]
5354+
};
5355+
53705356
let qualify = if self.parse_keyword(Keyword::QUALIFY) {
53715357
Some(self.parse_expr()?)
53725358
} else {
@@ -5386,6 +5372,7 @@ impl<'a> Parser<'a> {
53865372
distribute_by,
53875373
sort_by,
53885374
having,
5375+
named_window: named_windows,
53895376
qualify,
53905377
})
53915378
}
@@ -7033,6 +7020,39 @@ impl<'a> Parser<'a> {
70337020
pub fn index(&self) -> usize {
70347021
self.index
70357022
}
7023+
7024+
pub fn parse_named_window(&mut self) -> Result<NamedWindowDefinition, ParserError> {
7025+
let ident = self.parse_identifier()?;
7026+
self.expect_keyword(Keyword::AS)?;
7027+
self.expect_token(&Token::LParen)?;
7028+
let window_spec = self.parse_window_spec()?;
7029+
Ok(NamedWindowDefinition(ident, window_spec))
7030+
}
7031+
7032+
pub fn parse_window_spec(&mut self) -> Result<WindowSpec, ParserError> {
7033+
let partition_by = if self.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
7034+
self.parse_comma_separated(Parser::parse_expr)?
7035+
} else {
7036+
vec![]
7037+
};
7038+
let order_by = if self.parse_keywords(&[Keyword::ORDER, Keyword::BY]) {
7039+
self.parse_comma_separated(Parser::parse_order_by_expr)?
7040+
} else {
7041+
vec![]
7042+
};
7043+
let window_frame = if !self.consume_token(&Token::RParen) {
7044+
let window_frame = self.parse_window_frame()?;
7045+
self.expect_token(&Token::RParen)?;
7046+
Some(window_frame)
7047+
} else {
7048+
None
7049+
};
7050+
Ok(WindowSpec {
7051+
partition_by,
7052+
order_by,
7053+
window_frame,
7054+
})
7055+
}
70367056
}
70377057

70387058
impl Word {

tests/sqlparser_clickhouse.rs

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ fn parse_map_access_expr() {
101101
distribute_by: vec![],
102102
sort_by: vec![],
103103
having: None,
104+
named_window: vec![],
104105
qualify: None
105106
},
106107
select

0 commit comments

Comments
 (0)