Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 760
Support IGNORE|RESPECT NULLs clause in window functions#998
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
0a7123b78cad93c8ac7fd3035a79fa507d8c8cb2c45e5a04725da82a9819639e5b30e5504a215b7b9b30a9143096432c94File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1161,6 +1161,26 @@ impl fmt::Display for WindowFrameUnits { | ||
| } | ||
| } | ||
| /// Specifies Ignore / Respect NULL within window functions. | ||
| /// For example | ||
| /// `FIRST_VALUE(column2) IGNORE NULLS OVER (PARTITION BY column1)` | ||
| #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | ||
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | ||
| pub enum NullTreatment { | ||
| IgnoreNulls, | ||
| RespectNulls, | ||
| } | ||
| impl fmt::Display for NullTreatment { | ||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| f.write_str(match self { | ||
| NullTreatment::IgnoreNulls => "IGNORE NULLS", | ||
| NullTreatment::RespectNulls => "RESPECT NULLS", | ||
| }) | ||
| } | ||
| } | ||
| /// Specifies [WindowFrame]'s `start_bound` and `end_bound` | ||
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | ||
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||
| @@ -3757,6 +3777,8 @@ pub struct Function { | ||
| pub args: Vec<FunctionArg>, | ||
| /// e.g. `x > 5` in `COUNT(x) FILTER (WHERE x > 5)` | ||
| pub filter: Option<Box<Expr>>, | ||
| // Snowflake/MSSQL supports diffrent options for null treatment in rank functions | ||
| pub null_treatment: Option<NullTreatment>, | ||
yuval-illumex marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| pub over: Option<WindowType>, | ||
| // aggregate functions may specify eg `COUNT(DISTINCT x)` | ||
| pub distinct: bool, | ||
| @@ -3809,6 +3831,10 @@ impl fmt::Display for Function { | ||
| write!(f, " FILTER (WHERE {filter_cond})")?; | ||
| } | ||
| if let Some(o) = &self.null_treatment { | ||
| write!(f, " {o}")?; | ||
| } | ||
| if let Some(o) = &self.over { | ||
| write!(f, " OVER {o}")?; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -518,6 +518,7 @@ define_keywords!( | ||
| REPLACE, | ||
| REPLICATION, | ||
| RESET, | ||
| RESPECT, | ||
| RESTRICT, | ||
| RESULT, | ||
| RETAIN, | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -875,6 +875,7 @@ fn parse_select_count_wildcard() { | ||
| &Expr::Function(Function { | ||
| name: ObjectName(vec![Ident::new("COUNT")]), | ||
| args: vec![FunctionArg::Unnamed(FunctionArgExpr::Wildcard)], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: None, | ||
| distinct: false, | ||
| @@ -896,6 +897,7 @@ fn parse_select_count_distinct() { | ||
| op: UnaryOperator::Plus, | ||
| expr: Box::new(Expr::Identifier(Ident::new("x"))), | ||
| }))], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: None, | ||
| distinct: true, | ||
| @@ -1864,6 +1866,7 @@ fn parse_select_having() { | ||
| left: Box::new(Expr::Function(Function { | ||
| name: ObjectName(vec![Ident::new("COUNT")]), | ||
| args: vec![FunctionArg::Unnamed(FunctionArgExpr::Wildcard)], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: None, | ||
| distinct: false, | ||
| @@ -1890,6 +1893,7 @@ fn parse_select_qualify() { | ||
| left: Box::new(Expr::Function(Function { | ||
| name: ObjectName(vec![Ident::new("ROW_NUMBER")]), | ||
| args: vec![], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: Some(WindowType::WindowSpec(WindowSpec { | ||
| partition_by: vec![Expr::Identifier(Ident::new("p"))], | ||
| @@ -2287,6 +2291,45 @@ fn parse_agg_with_order_by() { | ||
| } | ||
| } | ||
| #[test] | ||
| fn parse_window_rank_function() { | ||
| let supported_dialects = TestedDialects { | ||
| dialects: vec![ | ||
| Box::new(GenericDialect {}), | ||
| Box::new(PostgreSqlDialect {}), | ||
| Box::new(MsSqlDialect {}), | ||
| Box::new(AnsiDialect {}), | ||
| Box::new(HiveDialect {}), | ||
| Box::new(SnowflakeDialect {}), | ||
| ], | ||
| options: None, | ||
| }; | ||
| for sql in [ | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why were these tests changed? This PR adds support for window functions (aka those that have an I think you should keep the existing tests and add a new one for the IGNORE NULLS / RESPECT NULLs syntax ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the feedback @alamb . The Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think that is true when the FIRST_VALUE function is being used as a window function Some systems allow SELECT FIRST_VALUE(amount ORDER BYtime), LAST_VALUE(amount ORDER BYtime)
FROM t
GROUP BY
currencyPerhaps @mustafasrepo can offer some perspective as the author of #882 But basically I think we should retain these tests
| ||
| "SELECT column1, column2, FIRST_VALUE(column2) OVER (PARTITION BY column1 ORDER BY column2 NULLS LAST) AS column2_first FROM t1", | ||
| "SELECT column1, column2, FIRST_VALUE(column2) OVER (ORDER BY column2 NULLS LAST) AS column2_first FROM t1", | ||
| "SELECT col_1, col_2, LAG(col_2) OVER (ORDER BY col_1) FROM t1", | ||
| "SELECT LAG(col_2, 1, 0) OVER (ORDER BY col_1) FROM t1", | ||
| "SELECT LAG(col_2, 1, 0) OVER (PARTITION BY col_3 ORDER BY col_1)", | ||
| ] { | ||
| supported_dialects.verified_stmt(sql); | ||
| } | ||
| let supported_dialects_nulls = TestedDialects { | ||
| dialects: vec![Box::new(MsSqlDialect {}), Box::new(SnowflakeDialect {})], | ||
| options: None, | ||
| }; | ||
| for sql in [ | ||
| "SELECT column1, column2, FIRST_VALUE(column2) IGNORE NULLS OVER (PARTITION BY column1 ORDER BY column2 NULLS LAST) AS column2_first FROM t1", | ||
| "SELECT column1, column2, FIRST_VALUE(column2) RESPECT NULLS OVER (PARTITION BY column1 ORDER BY column2 NULLS LAST) AS column2_first FROM t1", | ||
| "SELECT LAG(col_2, 1, 0) IGNORE NULLS OVER (ORDER BY col_1) FROM t1", | ||
| "SELECT LAG(col_2, 1, 0) RESPECT NULLS OVER (ORDER BY col_1) FROM t1", | ||
| ] { | ||
| supported_dialects_nulls.verified_stmt(sql); | ||
| } | ||
| } | ||
| #[test] | ||
| fn parse_create_table() { | ||
| let sql = "CREATE TABLE uk_cities (\ | ||
| @@ -3346,6 +3389,7 @@ fn parse_scalar_function_in_projection() { | ||
| args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( | ||
| Expr::Identifier(Ident::new("id")) | ||
| ))], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: None, | ||
| distinct: false, | ||
| @@ -3466,6 +3510,7 @@ fn parse_named_argument_function() { | ||
| ))), | ||
| }, | ||
| ], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: None, | ||
| distinct: false, | ||
| @@ -3498,6 +3543,7 @@ fn parse_window_functions() { | ||
| &Expr::Function(Function { | ||
| name: ObjectName(vec![Ident::new("row_number")]), | ||
| args: vec![], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: Some(WindowType::WindowSpec(WindowSpec { | ||
| partition_by: vec![], | ||
| @@ -3542,6 +3588,7 @@ fn test_parse_named_window() { | ||
| quote_style: None, | ||
| }), | ||
| ))], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: Some(WindowType::NamedWindow(Ident { | ||
| value: "window1".to_string(), | ||
| @@ -3568,6 +3615,7 @@ fn test_parse_named_window() { | ||
| quote_style: None, | ||
| }), | ||
| ))], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: Some(WindowType::NamedWindow(Ident { | ||
| value: "window2".to_string(), | ||
| @@ -4038,6 +4086,7 @@ fn parse_at_timezone() { | ||
| quote_style: None, | ||
| }]), | ||
| args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(zero.clone()))], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: None, | ||
| distinct: false, | ||
| @@ -4066,6 +4115,7 @@ fn parse_at_timezone() { | ||
| quote_style: None, | ||
| },],), | ||
| args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(zero))], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: None, | ||
| distinct: false, | ||
| @@ -4078,6 +4128,7 @@ fn parse_at_timezone() { | ||
| Value::SingleQuotedString("%Y-%m-%dT%H".to_string()), | ||
| ),),), | ||
| ], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: None, | ||
| distinct: false, | ||
| @@ -4237,6 +4288,7 @@ fn parse_table_function() { | ||
| args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( | ||
| Value::SingleQuotedString("1".to_owned()), | ||
| )))], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: None, | ||
| distinct: false, | ||
| @@ -4389,6 +4441,7 @@ fn parse_unnest_in_from_clause() { | ||
| FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("2")))), | ||
| FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("3")))), | ||
| ], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: None, | ||
| distinct: false, | ||
| @@ -4419,6 +4472,7 @@ fn parse_unnest_in_from_clause() { | ||
| FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("2")))), | ||
| FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("3")))), | ||
| ], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: None, | ||
| distinct: false, | ||
| @@ -4431,6 +4485,7 @@ fn parse_unnest_in_from_clause() { | ||
| FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("5")))), | ||
| FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("6")))), | ||
| ], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: None, | ||
| distinct: false, | ||
| @@ -6904,6 +6959,7 @@ fn parse_time_functions() { | ||
| let select_localtime_func_call_ast = Function { | ||
| name: ObjectName(vec![Ident::new(func_name)]), | ||
| args: vec![], | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: None, | ||
| distinct: false, | ||
| @@ -7391,6 +7447,7 @@ fn parse_pivot_table() { | ||
| args: (vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( | ||
| Expr::CompoundIdentifier(vec![Ident::new("a"), Ident::new("amount"),]) | ||
| ))]), | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: None, | ||
| distinct: false, | ||
| @@ -7541,6 +7598,7 @@ fn parse_pivot_unpivot_table() { | ||
| args: (vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( | ||
| Expr::Identifier(Ident::new("population")) | ||
| ))]), | ||
| null_treatment: None, | ||
| filter: None, | ||
| over: None, | ||
| distinct: false, | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.