mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-22 06:54:07 +00:00
Support named windows in OVER (window_definition)
clause (#1166)
Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
This commit is contained in:
parent
83c5d8191b
commit
23103302e6
4 changed files with 92 additions and 2 deletions
|
@ -1246,11 +1246,19 @@ impl Display for WindowType {
|
|||
}
|
||||
}
|
||||
|
||||
/// A window specification (i.e. `OVER (PARTITION BY .. ORDER BY .. etc.)`)
|
||||
/// A window specification (i.e. `OVER ([window_name] PARTITION BY .. ORDER BY .. etc.)`)
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub struct WindowSpec {
|
||||
/// Optional window name.
|
||||
///
|
||||
/// You can find it at least in [MySQL][1], [BigQuery][2], [PostgreSQL][3]
|
||||
///
|
||||
/// [1]: https://dev.mysql.com/doc/refman/8.0/en/window-functions-named-windows.html
|
||||
/// [2]: https://cloud.google.com/bigquery/docs/reference/standard-sql/window-function-calls
|
||||
/// [3]: https://www.postgresql.org/docs/current/sql-expressions.html#SYNTAX-WINDOW-FUNCTIONS
|
||||
pub window_name: Option<Ident>,
|
||||
/// `OVER (PARTITION BY ...)`
|
||||
pub partition_by: Vec<Expr>,
|
||||
/// `OVER (ORDER BY ...)`
|
||||
|
@ -1262,7 +1270,12 @@ pub struct WindowSpec {
|
|||
impl fmt::Display for WindowSpec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut delim = "";
|
||||
if let Some(window_name) = &self.window_name {
|
||||
delim = " ";
|
||||
write!(f, "{window_name}")?;
|
||||
}
|
||||
if !self.partition_by.is_empty() {
|
||||
f.write_str(delim)?;
|
||||
delim = " ";
|
||||
write!(
|
||||
f,
|
||||
|
|
|
@ -9528,6 +9528,13 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
|
||||
pub fn parse_window_spec(&mut self) -> Result<WindowSpec, ParserError> {
|
||||
let window_name = match self.peek_token().token {
|
||||
Token::Word(word) if word.keyword == Keyword::NoKeyword => {
|
||||
self.maybe_parse(|parser| parser.parse_identifier(false))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let partition_by = if self.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
|
||||
self.parse_comma_separated(Parser::parse_expr)?
|
||||
} else {
|
||||
|
@ -9538,6 +9545,7 @@ impl<'a> Parser<'a> {
|
|||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let window_frame = if !self.consume_token(&Token::RParen) {
|
||||
let window_frame = self.parse_window_frame()?;
|
||||
self.expect_token(&Token::RParen)?;
|
||||
|
@ -9546,6 +9554,7 @@ impl<'a> Parser<'a> {
|
|||
None
|
||||
};
|
||||
Ok(WindowSpec {
|
||||
window_name,
|
||||
partition_by,
|
||||
order_by,
|
||||
window_frame,
|
||||
|
|
|
@ -2062,6 +2062,7 @@ fn parse_select_qualify() {
|
|||
null_treatment: None,
|
||||
filter: None,
|
||||
over: Some(WindowType::WindowSpec(WindowSpec {
|
||||
window_name: None,
|
||||
partition_by: vec![Expr::Identifier(Ident::new("p"))],
|
||||
order_by: vec![OrderByExpr {
|
||||
expr: Expr::Identifier(Ident::new("o")),
|
||||
|
@ -4122,7 +4123,10 @@ fn parse_window_functions() {
|
|||
GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) \
|
||||
FROM foo";
|
||||
let select = verified_only_select(sql);
|
||||
assert_eq!(7, select.projection.len());
|
||||
|
||||
const EXPECTED_PROJ_QTY: usize = 7;
|
||||
assert_eq!(EXPECTED_PROJ_QTY, select.projection.len());
|
||||
|
||||
assert_eq!(
|
||||
&Expr::Function(Function {
|
||||
name: ObjectName(vec![Ident::new("row_number")]),
|
||||
|
@ -4130,6 +4134,7 @@ fn parse_window_functions() {
|
|||
null_treatment: None,
|
||||
filter: None,
|
||||
over: Some(WindowType::WindowSpec(WindowSpec {
|
||||
window_name: None,
|
||||
partition_by: vec![],
|
||||
order_by: vec![OrderByExpr {
|
||||
expr: Expr::Identifier(Ident::new("dt")),
|
||||
|
@ -4144,6 +4149,66 @@ fn parse_window_functions() {
|
|||
}),
|
||||
expr_from_projection(&select.projection[0])
|
||||
);
|
||||
|
||||
for i in 0..EXPECTED_PROJ_QTY {
|
||||
assert!(matches!(
|
||||
expr_from_projection(&select.projection[i]),
|
||||
Expr::Function(Function {
|
||||
over: Some(WindowType::WindowSpec(WindowSpec {
|
||||
window_name: None,
|
||||
..
|
||||
})),
|
||||
..
|
||||
})
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_named_window_functions() {
|
||||
let supported_dialects = TestedDialects {
|
||||
dialects: vec![
|
||||
Box::new(GenericDialect {}),
|
||||
Box::new(PostgreSqlDialect {}),
|
||||
Box::new(MySqlDialect {}),
|
||||
Box::new(BigQueryDialect {}),
|
||||
],
|
||||
options: None,
|
||||
};
|
||||
|
||||
let sql = "SELECT row_number() OVER (w ORDER BY dt DESC), \
|
||||
sum(foo) OVER (win PARTITION BY a, b ORDER BY c, d \
|
||||
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) \
|
||||
FROM foo \
|
||||
WINDOW w AS (PARTITION BY x), win AS (ORDER BY y)";
|
||||
supported_dialects.verified_stmt(sql);
|
||||
|
||||
let select = verified_only_select(sql);
|
||||
|
||||
const EXPECTED_PROJ_QTY: usize = 2;
|
||||
assert_eq!(EXPECTED_PROJ_QTY, select.projection.len());
|
||||
|
||||
const EXPECTED_WIN_NAMES: [&str; 2] = ["w", "win"];
|
||||
for (i, win_name) in EXPECTED_WIN_NAMES.iter().enumerate() {
|
||||
assert!(matches!(
|
||||
expr_from_projection(&select.projection[i]),
|
||||
Expr::Function(Function {
|
||||
over: Some(WindowType::WindowSpec(WindowSpec {
|
||||
window_name: Some(Ident { value, .. }),
|
||||
..
|
||||
})),
|
||||
..
|
||||
}) if value == win_name
|
||||
));
|
||||
}
|
||||
|
||||
let sql = "SELECT \
|
||||
FIRST_VALUE(x) OVER (w ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS first, \
|
||||
FIRST_VALUE(x) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS last, \
|
||||
SUM(y) OVER (win PARTITION BY x) AS last \
|
||||
FROM EMPLOYEE \
|
||||
WINDOW w AS (PARTITION BY x), win AS (w ORDER BY y)";
|
||||
supported_dialects.verified_stmt(sql);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -4244,6 +4309,7 @@ fn test_parse_named_window() {
|
|||
quote_style: None,
|
||||
},
|
||||
WindowSpec {
|
||||
window_name: None,
|
||||
partition_by: vec![],
|
||||
order_by: vec![OrderByExpr {
|
||||
expr: Expr::Identifier(Ident {
|
||||
|
@ -4262,6 +4328,7 @@ fn test_parse_named_window() {
|
|||
quote_style: None,
|
||||
},
|
||||
WindowSpec {
|
||||
window_name: None,
|
||||
partition_by: vec![Expr::Identifier(Ident {
|
||||
value: "C11".to_string(),
|
||||
quote_style: None,
|
||||
|
|
|
@ -446,6 +446,7 @@ fn parse_window_function_with_filter() {
|
|||
))],
|
||||
null_treatment: None,
|
||||
over: Some(WindowType::WindowSpec(WindowSpec {
|
||||
window_name: None,
|
||||
partition_by: vec![],
|
||||
order_by: vec![],
|
||||
window_frame: None,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue