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 <mustafa.akur@synnada.ai>
Co-authored-by: Mehmet Ozan Kabak <ozankabak@gmail.com>
This commit is contained in:
Berkay Şahin 2023-05-18 22:00:24 +03:00 committed by GitHub
parent 1b86abebe2
commit ef46cd3752
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 225 additions and 37 deletions

View file

@ -17,7 +17,7 @@ use alloc::{
string::{String, ToString},
vec::Vec,
};
use core::fmt;
use core::fmt::{self, Display};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
@ -35,10 +35,10 @@ pub use self::ddl::{
pub use self::operator::{BinaryOperator, UnaryOperator};
pub use self::query::{
Cte, Distinct, ExceptSelectItem, ExcludeSelectItem, Fetch, IdentWithAlias, Join,
JoinConstraint, JoinOperator, LateralView, LockClause, LockType, NonBlock, Offset, OffsetRows,
OrderByExpr, Query, RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select,
SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Table, TableAlias, TableFactor,
TableWithJoins, Top, Values, WildcardAdditionalOptions, With,
JoinConstraint, JoinOperator, LateralView, LockClause, LockType, NamedWindowDefinition,
NonBlock, Offset, OffsetRows, OrderByExpr, Query, RenameSelectItem, ReplaceSelectElement,
ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Table,
TableAlias, TableFactor, TableWithJoins, Top, Values, WildcardAdditionalOptions, With,
};
pub use self::value::{
escape_quoted_string, DateTimeField, DollarQuotedString, TrimWhereField, Value,
@ -930,6 +930,23 @@ impl fmt::Display for 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 WindowType {
WindowSpec(WindowSpec),
NamedWindow(Ident),
}
impl Display for WindowType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WindowType::WindowSpec(spec) => write!(f, "({})", spec),
WindowType::NamedWindow(name) => write!(f, "{}", name),
}
}
}
/// A window specification (i.e. `OVER (PARTITION BY .. ORDER BY .. etc.)`)
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -3360,7 +3377,7 @@ impl fmt::Display for CloseCursor {
pub struct Function {
pub name: ObjectName,
pub args: Vec<FunctionArg>,
pub over: Option<WindowSpec>,
pub over: Option<WindowType>,
// aggregate functions may specify eg `COUNT(DISTINCT x)`
pub distinct: bool,
// Some functions must be called without trailing parentheses, for example Postgres
@ -3409,7 +3426,7 @@ impl fmt::Display for Function {
)?;
if let Some(o) = &self.over {
write!(f, " OVER ({o})")?;
write!(f, " OVER {o}")?;
}
}

View file

@ -216,6 +216,8 @@ pub struct Select {
pub sort_by: Vec<Expr>,
/// HAVING
pub having: Option<Expr>,
/// WINDOW AS
pub named_window: Vec<NamedWindowDefinition>,
/// QUALIFY (Snowflake)
pub qualify: Option<Expr>,
}
@ -269,6 +271,9 @@ impl fmt::Display for Select {
if let Some(ref having) = self.having {
write!(f, " HAVING {having}")?;
}
if !self.named_window.is_empty() {
write!(f, " WINDOW {}", display_comma_separated(&self.named_window))?;
}
if let Some(ref qualify) = self.qualify {
write!(f, " QUALIFY {qualify}")?;
}
@ -311,6 +316,17 @@ impl fmt::Display for LateralView {
}
}
#[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);
impl fmt::Display for NamedWindowDefinition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} AS ({})", self.0, self.1)
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]

View file

@ -688,6 +688,7 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
Keyword::OUTER,
Keyword::SET,
Keyword::QUALIFY,
Keyword::WINDOW,
];
/// Can't be used as a column alias, so that `SELECT <expr> alias`

View file

@ -884,32 +884,12 @@ impl<'a> Parser<'a> {
let distinct = self.parse_all_or_distinct()?.is_some();
let (args, order_by) = self.parse_optional_args_with_orderby()?;
let over = if self.parse_keyword(Keyword::OVER) {
// TBD: support window names (`OVER mywin`) in place of inline specification
self.expect_token(&Token::LParen)?;
let partition_by = if self.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
// a list of possibly-qualified column names
self.parse_comma_separated(Parser::parse_expr)?
if self.consume_token(&Token::LParen) {
let window_spec = self.parse_window_spec()?;
Some(WindowType::WindowSpec(window_spec))
} else {
vec![]
};
let order_by = if self.parse_keywords(&[Keyword::ORDER, Keyword::BY]) {
self.parse_comma_separated(Parser::parse_order_by_expr)?
} else {
vec![]
};
let window_frame = if !self.consume_token(&Token::RParen) {
let window_frame = self.parse_window_frame()?;
self.expect_token(&Token::RParen)?;
Some(window_frame)
} else {
None
};
Some(WindowSpec {
partition_by,
order_by,
window_frame,
})
Some(WindowType::NamedWindow(self.parse_identifier()?))
}
} else {
None
};
@ -5367,6 +5347,12 @@ impl<'a> Parser<'a> {
None
};
let named_windows = if self.parse_keyword(Keyword::WINDOW) {
self.parse_comma_separated(Parser::parse_named_window)?
} else {
vec![]
};
let qualify = if self.parse_keyword(Keyword::QUALIFY) {
Some(self.parse_expr()?)
} else {
@ -5386,6 +5372,7 @@ impl<'a> Parser<'a> {
distribute_by,
sort_by,
having,
named_window: named_windows,
qualify,
})
}
@ -7033,6 +7020,39 @@ impl<'a> Parser<'a> {
pub fn index(&self) -> usize {
self.index
}
pub fn parse_named_window(&mut self) -> Result<NamedWindowDefinition, ParserError> {
let ident = self.parse_identifier()?;
self.expect_keyword(Keyword::AS)?;
self.expect_token(&Token::LParen)?;
let window_spec = self.parse_window_spec()?;
Ok(NamedWindowDefinition(ident, window_spec))
}
pub fn parse_window_spec(&mut self) -> Result<WindowSpec, ParserError> {
let partition_by = if self.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
self.parse_comma_separated(Parser::parse_expr)?
} else {
vec![]
};
let order_by = if self.parse_keywords(&[Keyword::ORDER, Keyword::BY]) {
self.parse_comma_separated(Parser::parse_order_by_expr)?
} else {
vec![]
};
let window_frame = if !self.consume_token(&Token::RParen) {
let window_frame = self.parse_window_frame()?;
self.expect_token(&Token::RParen)?;
Some(window_frame)
} else {
None
};
Ok(WindowSpec {
partition_by,
order_by,
window_frame,
})
}
}
impl Word {