From 0a7123b03e61f20cd62971c51aefb90ae397125c Mon Sep 17 00:00:00 2001 From: Yuval Shkolar Date: Wed, 11 Oct 2023 16:13:08 +0300 Subject: [PATCH 01/13] inital commit --- src/ast/mod.rs | 34 ++++++++++++++++++++++++++++ src/keywords.rs | 1 + src/parser/mod.rs | 44 ++++++++++++++++++++++++++++++++++++ tests/sqlparser_snowflake.rs | 37 ++++++++++++++++++++++++++++++ 4 files changed, 116 insertions(+) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 87f7ebb37..6c560be1d 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -595,6 +595,14 @@ pub enum Expr { /// `` opt_search_modifier: Option, }, + RankFunction { + name: ObjectName, + expr: Box, + offset: Option, + default: Option>, + respect_nulls: Option, + window: WindowSpec, + }, } impl fmt::Display for Expr { @@ -965,6 +973,32 @@ impl fmt::Display for Expr { Ok(()) } + Expr::RankFunction { + name, + expr, + offset, + default, + respect_nulls, + window, + } => { + write!(f, "{name}({expr}")?; + if let Some(offset_value) = offset { + write!(f, ",{offset_value}")?; + } + if let Some(default_value) = default { + write!(f, ",{default_value}")?; + } + write!(f, ") ")?; + if let Some(respect_nulls_value) = respect_nulls { + if *respect_nulls_value { + write!(f, "RESPECT NULLS ")?; + } else { + write!(f, "IGNORE NULLS ")?; + } + } + write!(f, "OVER ({window})")?; + Ok(()) + } } } } diff --git a/src/keywords.rs b/src/keywords.rs index e1bbf44ae..979be7320 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -516,6 +516,7 @@ define_keywords!( REPLACE, REPLICATION, RESET, + RESPECT, RESTRICT, RESULT, RETAIN, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 95f1f8edc..e4df2bab0 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -787,6 +787,9 @@ impl<'a> Parser<'a> { } Keyword::CASE => self.parse_case_expr(), Keyword::CAST => self.parse_cast_expr(), + Keyword::LAG | Keyword::FIRST_VALUE | Keyword::LAST_VALUE | Keyword::LEAD => { + self.parse_rank_functions(ObjectName(vec![w.to_ident()])) + } Keyword::TRY_CAST => self.parse_try_cast_expr(), Keyword::SAFE_CAST => self.parse_safe_cast_expr(), Keyword::EXISTS => self.parse_exists_expr(false), @@ -7397,6 +7400,47 @@ impl<'a> Parser<'a> { Ok(clauses) } + pub fn parse_rank_functions(&mut self, name: ObjectName) -> Result { + self.expect_token(&Token::LParen)?; + let expr = self.parse_expr()?; + let offset = if self.consume_token(&Token::Comma) { + Some(self.parse_number_value()?) + } else { + None + }; + let default = if self.consume_token(&Token::Comma) { + Some(self.parse_expr()?) + } else { + None + }; + self.expect_token(&Token::RParen)?; + + let respect_nulls; + if self.parse_keyword(Keyword::IGNORE) { + self.expect_keyword(Keyword::NULLS)?; + respect_nulls = Some(false) + } else if self.parse_keyword(Keyword::RESPECT) { + self.expect_keyword(Keyword::NULLS)?; + respect_nulls = Some(true) + } else { + respect_nulls = None + } + + self.expect_keyword(Keyword::OVER)?; + self.expect_token(&Token::LParen)?; + + let window = self.parse_window_spec()?; + + Ok(Expr::RankFunction { + name, + expr: Box::new(expr), + offset, + default: default.map(Box::new), + respect_nulls, + window, + }) + } + pub fn parse_merge(&mut self) -> Result { let into = self.parse_keyword(Keyword::INTO); diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index e92656d0b..cc73d3024 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -1064,3 +1064,40 @@ fn test_snowflake_trim() { snowflake().parse_sql_statements(error_sql).unwrap_err() ); } + +#[test] +fn test_snowflake_rank() { + let real_sql = "SELECT col_1, col_2, LAG(col_2) IGNORE NULLS OVER (ORDER BY col_1) FROM t1"; + assert_eq!(snowflake().verified_stmt(real_sql).to_string(), real_sql); + + let first_sql = "SELECT column1, column2, FIRST_VALUE(column2) OVER (PARTITION BY column1 ORDER BY column2 NULLS LAST) AS column2_first FROM t1"; + assert_eq!(snowflake().verified_stmt(first_sql).to_string(), first_sql); + + let sql_only_select = "SELECT LAG(col_2,1,0) OVER (PARTITION BY col_3 ORDER BY col_1)"; + let select = snowflake().verified_only_select(sql_only_select); + assert_eq!( + &Expr::RankFunction { + name: ObjectName(vec![Ident::new("LAG")]), + expr: Box::new(Expr::Identifier(Ident::new("col_2"))), + offset: Some(Value::Number("1".parse().unwrap(), false)), + default: Some(Box::new(Expr::Value(number("0")))), + respect_nulls: None, + window: WindowSpec { + partition_by: vec![Expr::Identifier(Ident { + value: "col_3".to_string(), + quote_style: None, + })], + order_by: vec![OrderByExpr { + expr: Expr::Identifier(Ident { + value: "col_1".to_string(), + quote_style: None, + }), + asc: None, + nulls_first: None, + }], + window_frame: None + } + }, + expr_from_projection(only(&select.projection)) + ); +} From 78cad93da923b7ca5b27040bab6b1ffe235eb332 Mon Sep 17 00:00:00 2001 From: Yuval Shkolar Date: Wed, 11 Oct 2023 16:23:50 +0300 Subject: [PATCH 02/13] fix test --- src/parser/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index e4df2bab0..af50fecb3 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -787,7 +787,7 @@ impl<'a> Parser<'a> { } Keyword::CASE => self.parse_case_expr(), Keyword::CAST => self.parse_cast_expr(), - Keyword::LAG | Keyword::FIRST_VALUE | Keyword::LAST_VALUE | Keyword::LEAD => { + Keyword::LAG | Keyword::FIRST_VALUE | Keyword::LAST_VALUE | Keyword::LEAD if dialect_of!(self is SnowflakeDialect) => { self.parse_rank_functions(ObjectName(vec![w.to_ident()])) } Keyword::TRY_CAST => self.parse_try_cast_expr(), From c8ac7fdc6fe72dca2e22a2083cd12c9c76bc413d Mon Sep 17 00:00:00 2001 From: Yuval Shkolar Date: Thu, 12 Oct 2023 13:23:22 +0300 Subject: [PATCH 03/13] extract enum --- src/ast/mod.rs | 29 ++++++++++++++------ src/parser/mod.rs | 13 ++++----- tests/sqlparser_common.rs | 52 +++++++++++++++++++++++++++++++++--- tests/sqlparser_snowflake.rs | 37 ------------------------- 4 files changed, 76 insertions(+), 55 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 6c560be1d..15855cabc 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -600,7 +600,7 @@ pub enum Expr { expr: Box, offset: Option, default: Option>, - respect_nulls: Option, + nulls_clause: Option, window: WindowSpec, }, } @@ -978,7 +978,7 @@ impl fmt::Display for Expr { expr, offset, default, - respect_nulls, + nulls_clause, window, } => { write!(f, "{name}({expr}")?; @@ -989,12 +989,8 @@ impl fmt::Display for Expr { write!(f, ",{default_value}")?; } write!(f, ") ")?; - if let Some(respect_nulls_value) = respect_nulls { - if *respect_nulls_value { - write!(f, "RESPECT NULLS ")?; - } else { - write!(f, "IGNORE NULLS ")?; - } + if let Some(o) = nulls_clause { + write!(f, "{o} ")?; } write!(f, "OVER ({window})")?; Ok(()) @@ -1112,6 +1108,23 @@ impl fmt::Display for WindowFrameUnits { } } +#[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 WindowFunctionOption { + IgnoreNulls, + RespectNulls, +} + +impl fmt::Display for WindowFunctionOption { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match self { + WindowFunctionOption::IgnoreNulls => "IGNORE NULLS", + WindowFunctionOption::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))] diff --git a/src/parser/mod.rs b/src/parser/mod.rs index af50fecb3..7b59bd98c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -787,7 +787,8 @@ impl<'a> Parser<'a> { } Keyword::CASE => self.parse_case_expr(), Keyword::CAST => self.parse_cast_expr(), - Keyword::LAG | Keyword::FIRST_VALUE | Keyword::LAST_VALUE | Keyword::LEAD if dialect_of!(self is SnowflakeDialect) => { + // Keyword::LAG | Keyword::FIRST_VALUE | Keyword::LAST_VALUE | Keyword::LEAD if dialect_of!(self is SnowflakeDialect) => { + Keyword::LAG | Keyword::FIRST_VALUE | Keyword::LAST_VALUE | Keyword::LEAD => { self.parse_rank_functions(ObjectName(vec![w.to_ident()])) } Keyword::TRY_CAST => self.parse_try_cast_expr(), @@ -7415,15 +7416,15 @@ impl<'a> Parser<'a> { }; self.expect_token(&Token::RParen)?; - let respect_nulls; + let nulls_clause; if self.parse_keyword(Keyword::IGNORE) { self.expect_keyword(Keyword::NULLS)?; - respect_nulls = Some(false) + nulls_clause = Some(WindowFunctionOption::IgnoreNulls) } else if self.parse_keyword(Keyword::RESPECT) { self.expect_keyword(Keyword::NULLS)?; - respect_nulls = Some(true) + nulls_clause = Some(WindowFunctionOption::RespectNulls) } else { - respect_nulls = None + nulls_clause = None } self.expect_keyword(Keyword::OVER)?; @@ -7436,7 +7437,7 @@ impl<'a> Parser<'a> { expr: Box::new(expr), offset, default: default.map(Box::new), - respect_nulls, + nulls_clause, window, }) } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 1511aa76e..b3bd65653 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2259,18 +2259,62 @@ fn parse_agg_with_order_by() { Box::new(MsSqlDialect {}), Box::new(AnsiDialect {}), Box::new(HiveDialect {}), + Box::new(SnowflakeDialect {}), ], options: None, }; for sql in [ - "SELECT FIRST_VALUE(x ORDER BY x) AS a FROM T", - "SELECT FIRST_VALUE(x ORDER BY x) FROM tbl", - "SELECT LAST_VALUE(x ORDER BY x, y) AS a FROM T", - "SELECT LAST_VALUE(x ORDER BY x ASC, y DESC) AS a FROM T", + "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); + } + + let sql_only_select = "SELECT LAG(col_2,1,0) IGNORE NULLS OVER (PARTITION BY col_3 ORDER BY col_1)"; + let select = supported_dialects_nulls.verified_only_select(sql_only_select); + assert_eq!( + &Expr::RankFunction { + name: ObjectName(vec![Ident::new("LAG")]), + expr: Box::new(Expr::Identifier(Ident::new("col_2"))), + offset: Some(Value::Number("1".parse().unwrap(), false)), + default: Some(Box::new(Expr::Value(number("0")))), + nulls_clause: None, + window: WindowSpec { + partition_by: vec![Expr::Identifier(Ident { + value: "col_3".to_string(), + quote_style: None, + })], + order_by: vec![OrderByExpr { + expr: Expr::Identifier(Ident { + value: "col_1".to_string(), + quote_style: None, + }), + asc: None, + nulls_first: None, + }], + window_frame: None + } + }, + expr_from_projection(only(&select.projection)) + ); } #[test] diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index cc73d3024..e92656d0b 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -1064,40 +1064,3 @@ fn test_snowflake_trim() { snowflake().parse_sql_statements(error_sql).unwrap_err() ); } - -#[test] -fn test_snowflake_rank() { - let real_sql = "SELECT col_1, col_2, LAG(col_2) IGNORE NULLS OVER (ORDER BY col_1) FROM t1"; - assert_eq!(snowflake().verified_stmt(real_sql).to_string(), real_sql); - - let first_sql = "SELECT column1, column2, FIRST_VALUE(column2) OVER (PARTITION BY column1 ORDER BY column2 NULLS LAST) AS column2_first FROM t1"; - assert_eq!(snowflake().verified_stmt(first_sql).to_string(), first_sql); - - let sql_only_select = "SELECT LAG(col_2,1,0) OVER (PARTITION BY col_3 ORDER BY col_1)"; - let select = snowflake().verified_only_select(sql_only_select); - assert_eq!( - &Expr::RankFunction { - name: ObjectName(vec![Ident::new("LAG")]), - expr: Box::new(Expr::Identifier(Ident::new("col_2"))), - offset: Some(Value::Number("1".parse().unwrap(), false)), - default: Some(Box::new(Expr::Value(number("0")))), - respect_nulls: None, - window: WindowSpec { - partition_by: vec![Expr::Identifier(Ident { - value: "col_3".to_string(), - quote_style: None, - })], - order_by: vec![OrderByExpr { - expr: Expr::Identifier(Ident { - value: "col_1".to_string(), - quote_style: None, - }), - asc: None, - nulls_first: None, - }], - window_frame: None - } - }, - expr_from_projection(only(&select.projection)) - ); -} From 3035a7955f4571cd4c5ff4d670bb363fa13f05e9 Mon Sep 17 00:00:00 2001 From: Yuval Shkolar Date: Thu, 12 Oct 2023 15:21:47 +0300 Subject: [PATCH 04/13] refactor --- src/ast/mod.rs | 44 ++++++------------------- src/parser/mod.rs | 68 ++++++++++++--------------------------- tests/sqlparser_common.rs | 36 +++------------------ 3 files changed, 34 insertions(+), 114 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 15855cabc..2afc5b76f 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -595,14 +595,6 @@ pub enum Expr { /// `` opt_search_modifier: Option, }, - RankFunction { - name: ObjectName, - expr: Box, - offset: Option, - default: Option>, - nulls_clause: Option, - window: WindowSpec, - }, } impl fmt::Display for Expr { @@ -973,28 +965,6 @@ impl fmt::Display for Expr { Ok(()) } - Expr::RankFunction { - name, - expr, - offset, - default, - nulls_clause, - window, - } => { - write!(f, "{name}({expr}")?; - if let Some(offset_value) = offset { - write!(f, ",{offset_value}")?; - } - if let Some(default_value) = default { - write!(f, ",{default_value}")?; - } - write!(f, ") ")?; - if let Some(o) = nulls_clause { - write!(f, "{o} ")?; - } - write!(f, "OVER ({window})")?; - Ok(()) - } } } } @@ -1010,7 +980,7 @@ pub enum WindowType { impl Display for WindowType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - WindowType::WindowSpec(spec) => write!(f, "({})", spec), + WindowType::WindowSpec(spec) => write!(f, "{}", spec), WindowType::NamedWindow(name) => write!(f, "{}", name), } } @@ -1021,6 +991,7 @@ impl Display for WindowType { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct WindowSpec { + pub nulls_clause: Option, pub partition_by: Vec, pub order_by: Vec, pub window_frame: Option, @@ -1029,6 +1000,10 @@ pub struct WindowSpec { impl fmt::Display for WindowSpec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut delim = ""; + if let Some(o) = &self.nulls_clause { + write!(f, "{o}")?; + } + write!(f, "OVER (")?; if !self.partition_by.is_empty() { delim = " "; write!( @@ -1054,6 +1029,7 @@ impl fmt::Display for WindowSpec { write!(f, "{} {}", window_frame.units, window_frame.start_bound)?; } } + write!(f, ")")?; Ok(()) } } @@ -1119,8 +1095,8 @@ pub enum WindowFunctionOption { impl fmt::Display for WindowFunctionOption { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match self { - WindowFunctionOption::IgnoreNulls => "IGNORE NULLS", - WindowFunctionOption::RespectNulls => "RESPECT NULLS", + WindowFunctionOption::IgnoreNulls => "IGNORE NULLS ", + WindowFunctionOption::RespectNulls => "RESPECT NULLS ", }) } } @@ -3746,7 +3722,7 @@ impl fmt::Display for Function { )?; if let Some(o) = &self.over { - write!(f, " OVER {o}")?; + write!(f, " {o}")?; } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 7b59bd98c..20f1886db 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -787,10 +787,7 @@ impl<'a> Parser<'a> { } Keyword::CASE => self.parse_case_expr(), Keyword::CAST => self.parse_cast_expr(), - // Keyword::LAG | Keyword::FIRST_VALUE | Keyword::LAST_VALUE | Keyword::LEAD if dialect_of!(self is SnowflakeDialect) => { - Keyword::LAG | Keyword::FIRST_VALUE | Keyword::LAST_VALUE | Keyword::LEAD => { - self.parse_rank_functions(ObjectName(vec![w.to_ident()])) - } + Keyword::TRY_CAST => self.parse_try_cast_expr(), Keyword::SAFE_CAST => self.parse_safe_cast_expr(), Keyword::EXISTS => self.parse_exists_expr(false), @@ -961,9 +958,21 @@ impl<'a> Parser<'a> { self.expect_token(&Token::LParen)?; let distinct = self.parse_all_or_distinct()?.is_some(); let (args, order_by) = self.parse_optional_args_with_orderby()?; + let nulls_clause = match self.parse_one_of_keywords(&[Keyword::RESPECT, Keyword::IGNORE]) { + Some(keyword) => { + self.expect_keyword(Keyword::NULLS)?; + + match keyword { + Keyword::RESPECT => Some(WindowFunctionOption::RespectNulls), + Keyword::IGNORE => Some(WindowFunctionOption::IgnoreNulls), + _ => None, + } + } + None => None, + }; let over = if self.parse_keyword(Keyword::OVER) { if self.consume_token(&Token::LParen) { - let window_spec = self.parse_window_spec()?; + let window_spec = self.parse_window_spec(nulls_clause)?; Some(WindowType::WindowSpec(window_spec)) } else { Some(WindowType::NamedWindow(self.parse_identifier()?)) @@ -7401,47 +7410,6 @@ impl<'a> Parser<'a> { Ok(clauses) } - pub fn parse_rank_functions(&mut self, name: ObjectName) -> Result { - self.expect_token(&Token::LParen)?; - let expr = self.parse_expr()?; - let offset = if self.consume_token(&Token::Comma) { - Some(self.parse_number_value()?) - } else { - None - }; - let default = if self.consume_token(&Token::Comma) { - Some(self.parse_expr()?) - } else { - None - }; - self.expect_token(&Token::RParen)?; - - let nulls_clause; - if self.parse_keyword(Keyword::IGNORE) { - self.expect_keyword(Keyword::NULLS)?; - nulls_clause = Some(WindowFunctionOption::IgnoreNulls) - } else if self.parse_keyword(Keyword::RESPECT) { - self.expect_keyword(Keyword::NULLS)?; - nulls_clause = Some(WindowFunctionOption::RespectNulls) - } else { - nulls_clause = None - } - - self.expect_keyword(Keyword::OVER)?; - self.expect_token(&Token::LParen)?; - - let window = self.parse_window_spec()?; - - Ok(Expr::RankFunction { - name, - expr: Box::new(expr), - offset, - default: default.map(Box::new), - nulls_clause, - window, - }) - } - pub fn parse_merge(&mut self) -> Result { let into = self.parse_keyword(Keyword::INTO); @@ -7574,7 +7542,7 @@ impl<'a> Parser<'a> { let ident = self.parse_identifier()?; self.expect_keyword(Keyword::AS)?; self.expect_token(&Token::LParen)?; - let window_spec = self.parse_window_spec()?; + let window_spec = self.parse_window_spec(None)?; Ok(NamedWindowDefinition(ident, window_spec)) } @@ -7593,7 +7561,10 @@ impl<'a> Parser<'a> { }) } - pub fn parse_window_spec(&mut self) -> Result { + pub fn parse_window_spec( + &mut self, + nulls_clause: Option, + ) -> Result { let partition_by = if self.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) { self.parse_comma_separated(Parser::parse_expr)? } else { @@ -7612,6 +7583,7 @@ impl<'a> Parser<'a> { None }; Ok(WindowSpec { + nulls_clause, partition_by, order_by, window_frame, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index b3bd65653..3914fad19 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2268,8 +2268,8 @@ fn parse_agg_with_order_by() { "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)", + "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); } @@ -2282,39 +2282,11 @@ fn parse_agg_with_order_by() { 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", + "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); } - - let sql_only_select = "SELECT LAG(col_2,1,0) IGNORE NULLS OVER (PARTITION BY col_3 ORDER BY col_1)"; - let select = supported_dialects_nulls.verified_only_select(sql_only_select); - assert_eq!( - &Expr::RankFunction { - name: ObjectName(vec![Ident::new("LAG")]), - expr: Box::new(Expr::Identifier(Ident::new("col_2"))), - offset: Some(Value::Number("1".parse().unwrap(), false)), - default: Some(Box::new(Expr::Value(number("0")))), - nulls_clause: None, - window: WindowSpec { - partition_by: vec![Expr::Identifier(Ident { - value: "col_3".to_string(), - quote_style: None, - })], - order_by: vec![OrderByExpr { - expr: Expr::Identifier(Ident { - value: "col_1".to_string(), - quote_style: None, - }), - asc: None, - nulls_first: None, - }], - window_frame: None - } - }, - expr_from_projection(only(&select.projection)) - ); } #[test] From fa507d8a91b2b57339fb6675acba35ab7143f613 Mon Sep 17 00:00:00 2001 From: Yuval Shkolar Date: Thu, 12 Oct 2023 15:25:12 +0300 Subject: [PATCH 05/13] merge fixes --- src/parser/mod.rs | 1 - tests/sqlparser_common.rs | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 20f1886db..1f23e9896 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -787,7 +787,6 @@ impl<'a> Parser<'a> { } Keyword::CASE => self.parse_case_expr(), Keyword::CAST => self.parse_cast_expr(), - Keyword::TRY_CAST => self.parse_try_cast_expr(), Keyword::SAFE_CAST => self.parse_safe_cast_expr(), Keyword::EXISTS => self.parse_exists_expr(false), diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 3914fad19..3e8299296 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -1888,6 +1888,7 @@ fn parse_select_qualify() { name: ObjectName(vec![Ident::new("ROW_NUMBER")]), args: vec![], over: Some(WindowType::WindowSpec(WindowSpec { + nulls_clause: None, partition_by: vec![Expr::Identifier(Ident::new("p"))], order_by: vec![OrderByExpr { expr: Expr::Identifier(Ident::new("o")), @@ -3499,6 +3500,7 @@ fn parse_window_functions() { name: ObjectName(vec![Ident::new("row_number")]), args: vec![], over: Some(WindowType::WindowSpec(WindowSpec { + nulls_clause: None, partition_by: vec![], order_by: vec![OrderByExpr { expr: Expr::Identifier(Ident::new("dt")), @@ -3609,6 +3611,7 @@ fn test_parse_named_window() { quote_style: None, }, WindowSpec { + nulls_clause: None, partition_by: vec![], order_by: vec![OrderByExpr { expr: Expr::Identifier(Ident { @@ -3627,6 +3630,7 @@ fn test_parse_named_window() { quote_style: None, }, WindowSpec { + nulls_clause: None, partition_by: vec![Expr::Identifier(Ident { value: "C11".to_string(), quote_style: None, From c8cb2c4dbf6fd814e7c5a04d41d5883a2bd1a115 Mon Sep 17 00:00:00 2001 From: Yuval Shkolar Date: Thu, 12 Oct 2023 16:04:49 +0300 Subject: [PATCH 06/13] move null_clause --- src/ast/mod.rs | 19 +++++++++---------- src/parser/mod.rs | 9 +++++---- tests/sqlparser_bigquery.rs | 1 + tests/sqlparser_clickhouse.rs | 4 ++++ tests/sqlparser_common.rs | 23 +++++++++++++++++++---- tests/sqlparser_hive.rs | 1 + tests/sqlparser_mssql.rs | 1 + tests/sqlparser_mysql.rs | 6 ++++++ tests/sqlparser_postgres.rs | 6 ++++++ tests/sqlparser_redshift.rs | 1 + tests/sqlparser_snowflake.rs | 23 +++++++++++++++++++++++ 11 files changed, 76 insertions(+), 18 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 2afc5b76f..72254fc34 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -980,7 +980,7 @@ pub enum WindowType { impl Display for WindowType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - WindowType::WindowSpec(spec) => write!(f, "{}", spec), + WindowType::WindowSpec(spec) => write!(f, "({})", spec), WindowType::NamedWindow(name) => write!(f, "{}", name), } } @@ -991,7 +991,6 @@ impl Display for WindowType { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct WindowSpec { - pub nulls_clause: Option, pub partition_by: Vec, pub order_by: Vec, pub window_frame: Option, @@ -1000,10 +999,6 @@ pub struct WindowSpec { impl fmt::Display for WindowSpec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut delim = ""; - if let Some(o) = &self.nulls_clause { - write!(f, "{o}")?; - } - write!(f, "OVER (")?; if !self.partition_by.is_empty() { delim = " "; write!( @@ -1029,7 +1024,6 @@ impl fmt::Display for WindowSpec { write!(f, "{} {}", window_frame.units, window_frame.start_bound)?; } } - write!(f, ")")?; Ok(()) } } @@ -1095,8 +1089,8 @@ pub enum WindowFunctionOption { impl fmt::Display for WindowFunctionOption { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match self { - WindowFunctionOption::IgnoreNulls => "IGNORE NULLS ", - WindowFunctionOption::RespectNulls => "RESPECT NULLS ", + WindowFunctionOption::IgnoreNulls => "IGNORE NULLS", + WindowFunctionOption::RespectNulls => "RESPECT NULLS", }) } } @@ -3673,6 +3667,7 @@ impl fmt::Display for CloseCursor { pub struct Function { pub name: ObjectName, pub args: Vec, + pub nulls_clause: Option, pub over: Option, // aggregate functions may specify eg `COUNT(DISTINCT x)` pub distinct: bool, @@ -3721,9 +3716,13 @@ impl fmt::Display for Function { display_comma_separated(&self.order_by), )?; - if let Some(o) = &self.over { + if let Some(o) = &self.nulls_clause { write!(f, " {o}")?; } + + if let Some(o) = &self.over { + write!(f, " OVER {o}")?; + } } Ok(()) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 1f23e9896..f13916f98 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -772,6 +772,7 @@ impl<'a> Parser<'a> { Ok(Expr::Function(Function { name: ObjectName(vec![w.to_ident()]), args: vec![], + nulls_clause: None, over: None, distinct: false, special: true, @@ -971,7 +972,7 @@ impl<'a> Parser<'a> { }; let over = if self.parse_keyword(Keyword::OVER) { if self.consume_token(&Token::LParen) { - let window_spec = self.parse_window_spec(nulls_clause)?; + let window_spec = self.parse_window_spec()?; Some(WindowType::WindowSpec(window_spec)) } else { Some(WindowType::NamedWindow(self.parse_identifier()?)) @@ -982,6 +983,7 @@ impl<'a> Parser<'a> { Ok(Expr::Function(Function { name, args, + nulls_clause, over, distinct, special: false, @@ -999,6 +1001,7 @@ impl<'a> Parser<'a> { Ok(Expr::Function(Function { name, args, + nulls_clause: None, over: None, distinct: false, special, @@ -7541,7 +7544,7 @@ impl<'a> Parser<'a> { let ident = self.parse_identifier()?; self.expect_keyword(Keyword::AS)?; self.expect_token(&Token::LParen)?; - let window_spec = self.parse_window_spec(None)?; + let window_spec = self.parse_window_spec()?; Ok(NamedWindowDefinition(ident, window_spec)) } @@ -7562,7 +7565,6 @@ impl<'a> Parser<'a> { pub fn parse_window_spec( &mut self, - nulls_clause: Option, ) -> Result { let partition_by = if self.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) { self.parse_comma_separated(Parser::parse_expr)? @@ -7582,7 +7584,6 @@ impl<'a> Parser<'a> { None }; Ok(WindowSpec { - nulls_clause, partition_by, order_by, window_frame, diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index 7a9a8d1c4..1ff5d5f50 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -533,6 +533,7 @@ fn parse_map_access_offset() { args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( number("0") ))),], + nulls_clause: None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index 9efe4a368..481c801c9 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -50,6 +50,7 @@ fn parse_map_access_expr() { Value::SingleQuotedString("endpoint".to_string()) ))), ], + nulls_clause: None, over: None, distinct: false, special: false, @@ -89,6 +90,7 @@ fn parse_map_access_expr() { Value::SingleQuotedString("app".to_string()) ))), ], + nulls_clause: None, over: None, distinct: false, special: false, @@ -138,6 +140,7 @@ fn parse_array_fn() { FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Identifier(Ident::new("x1")))), FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Identifier(Ident::new("x2")))), ], + nulls_clause: None, over: None, distinct: false, special: false, @@ -196,6 +199,7 @@ fn parse_delimited_identifiers() { &Expr::Function(Function { name: ObjectName(vec![Ident::with_quote('"', "myfun")]), args: vec![], + nulls_clause: None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 3e8299296..ed4bf70fd 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -875,6 +875,7 @@ fn parse_select_count_wildcard() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("COUNT")]), args: vec![FunctionArg::Unnamed(FunctionArgExpr::Wildcard)], + nulls_clause: None, over: None, distinct: false, special: false, @@ -895,6 +896,7 @@ fn parse_select_count_distinct() { op: UnaryOperator::Plus, expr: Box::new(Expr::Identifier(Ident::new("x"))), }))], + nulls_clause: None, over: None, distinct: true, special: false, @@ -1862,6 +1864,7 @@ fn parse_select_having() { left: Box::new(Expr::Function(Function { name: ObjectName(vec![Ident::new("COUNT")]), args: vec![FunctionArg::Unnamed(FunctionArgExpr::Wildcard)], + nulls_clause: None, over: None, distinct: false, special: false, @@ -1887,8 +1890,8 @@ fn parse_select_qualify() { left: Box::new(Expr::Function(Function { name: ObjectName(vec![Ident::new("ROW_NUMBER")]), args: vec![], + nulls_clause: None, over: Some(WindowType::WindowSpec(WindowSpec { - nulls_clause: None, partition_by: vec![Expr::Identifier(Ident::new("p"))], order_by: vec![OrderByExpr { expr: Expr::Identifier(Ident::new("o")), @@ -3349,6 +3352,7 @@ fn parse_scalar_function_in_projection() { args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( Expr::Identifier(Ident::new("id")) ))], + nulls_clause: None, over: None, distinct: false, special: false, @@ -3468,6 +3472,7 @@ fn parse_named_argument_function() { ))), }, ], + nulls_clause: None, over: None, distinct: false, special: false, @@ -3499,8 +3504,8 @@ fn parse_window_functions() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("row_number")]), args: vec![], + nulls_clause: None, over: Some(WindowType::WindowSpec(WindowSpec { - nulls_clause: None, partition_by: vec![], order_by: vec![OrderByExpr { expr: Expr::Identifier(Ident::new("dt")), @@ -3543,6 +3548,7 @@ fn test_parse_named_window() { quote_style: None, }), ))], + nulls_clause: None, over: Some(WindowType::NamedWindow(Ident { value: "window1".to_string(), quote_style: None, @@ -3568,6 +3574,7 @@ fn test_parse_named_window() { quote_style: None, }), ))], + nulls_clause: None, over: Some(WindowType::NamedWindow(Ident { value: "window2".to_string(), quote_style: None, @@ -3611,7 +3618,6 @@ fn test_parse_named_window() { quote_style: None, }, WindowSpec { - nulls_clause: None, partition_by: vec![], order_by: vec![OrderByExpr { expr: Expr::Identifier(Ident { @@ -3630,7 +3636,6 @@ fn test_parse_named_window() { quote_style: None, }, WindowSpec { - nulls_clause: None, partition_by: vec![Expr::Identifier(Ident { value: "C11".to_string(), quote_style: None, @@ -4039,6 +4044,7 @@ fn parse_at_timezone() { quote_style: None, }]), args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(zero.clone()))], + nulls_clause: None, over: None, distinct: false, special: false, @@ -4066,6 +4072,7 @@ fn parse_at_timezone() { quote_style: None, },],), args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(zero))], + nulls_clause: None, over: None, distinct: false, special: false, @@ -4077,6 +4084,7 @@ fn parse_at_timezone() { Value::SingleQuotedString("%Y-%m-%dT%H".to_string()), ),),), ], + nulls_clause: None, over: None, distinct: false, special: false, @@ -4235,6 +4243,7 @@ fn parse_table_function() { args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( Value::SingleQuotedString("1".to_owned()), )))], + nulls_clause: None, over: None, distinct: false, special: false, @@ -4386,6 +4395,7 @@ fn parse_unnest_in_from_clause() { FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("2")))), FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("3")))), ], + nulls_clause: None, over: None, distinct: false, special: false, @@ -4415,6 +4425,7 @@ fn parse_unnest_in_from_clause() { FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("2")))), FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("3")))), ], + nulls_clause: None, over: None, distinct: false, special: false, @@ -4426,6 +4437,7 @@ fn parse_unnest_in_from_clause() { FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("5")))), FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("6")))), ], + nulls_clause: None, over: None, distinct: false, special: false, @@ -6898,6 +6910,7 @@ fn parse_time_functions() { let select_localtime_func_call_ast = Function { name: ObjectName(vec![Ident::new(func_name)]), args: vec![], + nulls_clause: None, over: None, distinct: false, special: false, @@ -7384,6 +7397,7 @@ fn parse_pivot_table() { args: (vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( Expr::CompoundIdentifier(vec![Ident::new("a"), Ident::new("amount"),]) ))]), + nulls_clause: None, over: None, distinct: false, special: false, @@ -7533,6 +7547,7 @@ fn parse_pivot_unpivot_table() { args: (vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( Expr::Identifier(Ident::new("population")) ))]), + nulls_clause: None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs index 6ca47e12c..a26f3d24d 100644 --- a/tests/sqlparser_hive.rs +++ b/tests/sqlparser_hive.rs @@ -346,6 +346,7 @@ fn parse_delimited_identifiers() { &Expr::Function(Function { name: ObjectName(vec![Ident::with_quote('"', "myfun")]), args: vec![], + nulls_clause: None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index f9eb4d8fb..de701d2e5 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -334,6 +334,7 @@ fn parse_delimited_identifiers() { &Expr::Function(Function { name: ObjectName(vec![Ident::with_quote('"', "myfun")]), args: vec![], + nulls_clause: None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 80b9dcfd8..52b56c6bd 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -1071,6 +1071,7 @@ fn parse_insert_with_on_duplicate_update() { args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( Expr::Identifier(Ident::new("description")) ))], + nulls_clause: None, over: None, distinct: false, special: false, @@ -1084,6 +1085,7 @@ fn parse_insert_with_on_duplicate_update() { args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( Expr::Identifier(Ident::new("perm_create")) ))], + nulls_clause: None, over: None, distinct: false, special: false, @@ -1097,6 +1099,7 @@ fn parse_insert_with_on_duplicate_update() { args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( Expr::Identifier(Ident::new("perm_read")) ))], + nulls_clause: None, over: None, distinct: false, special: false, @@ -1110,6 +1113,7 @@ fn parse_insert_with_on_duplicate_update() { args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( Expr::Identifier(Ident::new("perm_update")) ))], + nulls_clause: None, over: None, distinct: false, special: false, @@ -1123,6 +1127,7 @@ fn parse_insert_with_on_duplicate_update() { args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( Expr::Identifier(Ident::new("perm_delete")) ))], + nulls_clause: None, over: None, distinct: false, special: false, @@ -1500,6 +1505,7 @@ fn parse_table_colum_option_on_update() { option: ColumnOption::OnUpdate(Expr::Function(Function { name: ObjectName(vec![Ident::new("CURRENT_TIMESTAMP")]), args: vec![], + nulls_clause: None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index fe336bda7..97522da96 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -2274,6 +2274,7 @@ fn test_composite_value() { named: true } )))], + nulls_clause:None, over: None, distinct: false, special: false, @@ -2435,6 +2436,7 @@ fn parse_current_functions() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("CURRENT_CATALOG")]), args: vec![], + nulls_clause:None, over: None, distinct: false, special: true, @@ -2446,6 +2448,7 @@ fn parse_current_functions() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("CURRENT_USER")]), args: vec![], + nulls_clause:None, over: None, distinct: false, special: true, @@ -2457,6 +2460,7 @@ fn parse_current_functions() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("SESSION_USER")]), args: vec![], + nulls_clause:None, over: None, distinct: false, special: true, @@ -2468,6 +2472,7 @@ fn parse_current_functions() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("USER")]), args: vec![], + nulls_clause:None, over: None, distinct: false, special: true, @@ -2918,6 +2923,7 @@ fn parse_delimited_identifiers() { &Expr::Function(Function { name: ObjectName(vec![Ident::with_quote('"', "myfun")]), args: vec![], + nulls_clause:None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index 5ae539b3c..08f0c94a9 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs @@ -137,6 +137,7 @@ fn parse_delimited_identifiers() { &Expr::Function(Function { name: ObjectName(vec![Ident::with_quote('"', "myfun")]), args: vec![], + nulls_clause:None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index e92656d0b..8c5120e4d 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -247,6 +247,7 @@ fn parse_delimited_identifiers() { &Expr::Function(Function { name: ObjectName(vec![Ident::with_quote('"', "myfun")]), args: vec![], + nulls_clause: None, over: None, distinct: false, special: false, @@ -1064,3 +1065,25 @@ fn test_snowflake_trim() { snowflake().parse_sql_statements(error_sql).unwrap_err() ); } + +#[test] +fn parse_agg_with_order_by() { + for sql in [ + "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)", + ] { + snowflake().verified_stmt(sql); + } + + 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", + ] { + snowflake().verified_stmt(sql); + } +} From 5e5a04711b61dfee82d64061e8b0cccf05d105e1 Mon Sep 17 00:00:00 2001 From: Yuval Shkolar Date: Thu, 12 Oct 2023 16:05:11 +0300 Subject: [PATCH 07/13] lint --- src/parser/mod.rs | 4 +--- tests/sqlparser_postgres.rs | 12 ++++++------ tests/sqlparser_redshift.rs | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f13916f98..ba5a91f31 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -7563,9 +7563,7 @@ impl<'a> Parser<'a> { }) } - pub fn parse_window_spec( - &mut self, - ) -> Result { + pub fn parse_window_spec(&mut self) -> Result { let partition_by = if self.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) { self.parse_comma_separated(Parser::parse_expr)? } else { diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 97522da96..3d0f8cee2 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -2274,7 +2274,7 @@ fn test_composite_value() { named: true } )))], - nulls_clause:None, + nulls_clause: None, over: None, distinct: false, special: false, @@ -2436,7 +2436,7 @@ fn parse_current_functions() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("CURRENT_CATALOG")]), args: vec![], - nulls_clause:None, + nulls_clause: None, over: None, distinct: false, special: true, @@ -2448,7 +2448,7 @@ fn parse_current_functions() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("CURRENT_USER")]), args: vec![], - nulls_clause:None, + nulls_clause: None, over: None, distinct: false, special: true, @@ -2460,7 +2460,7 @@ fn parse_current_functions() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("SESSION_USER")]), args: vec![], - nulls_clause:None, + nulls_clause: None, over: None, distinct: false, special: true, @@ -2472,7 +2472,7 @@ fn parse_current_functions() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("USER")]), args: vec![], - nulls_clause:None, + nulls_clause: None, over: None, distinct: false, special: true, @@ -2923,7 +2923,7 @@ fn parse_delimited_identifiers() { &Expr::Function(Function { name: ObjectName(vec![Ident::with_quote('"', "myfun")]), args: vec![], - nulls_clause:None, + nulls_clause: None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index 08f0c94a9..50dcd07d8 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs @@ -137,7 +137,7 @@ fn parse_delimited_identifiers() { &Expr::Function(Function { name: ObjectName(vec![Ident::with_quote('"', "myfun")]), args: vec![], - nulls_clause:None, + nulls_clause: None, over: None, distinct: false, special: false, From 25da82ad0df9c5a37f890b8a63e83d68b29c838f Mon Sep 17 00:00:00 2001 From: Yuval Shkolar Date: Thu, 12 Oct 2023 16:09:29 +0300 Subject: [PATCH 08/13] try to fix tests --- src/ast/visitor.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ast/visitor.rs b/src/ast/visitor.rs index 09cb20a0c..00e45372c 100644 --- a/src/ast/visitor.rs +++ b/src/ast/visitor.rs @@ -506,6 +506,7 @@ where /// *expr = Expr::Function(Function { /// name: ObjectName(vec![Ident::new("f")]), /// args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(old_expr))], +/// nulls_clause: None, /// over: None, distinct: false, special: false, order_by: vec![], /// }); /// } From 98196392747b35611f6db569c82aa214a294334a Mon Sep 17 00:00:00 2001 From: Yuval Shkolar Date: Thu, 12 Oct 2023 20:59:25 +0300 Subject: [PATCH 09/13] rename --- src/ast/mod.rs | 12 +++++------ src/ast/visitor.rs | 2 +- src/parser/mod.rs | 13 ++++++------ test.sql | 8 ++++++++ tests/sqlparser_bigquery.rs | 2 +- tests/sqlparser_clickhouse.rs | 8 ++++---- tests/sqlparser_common.rs | 38 +++++++++++++++++------------------ tests/sqlparser_hive.rs | 2 +- tests/sqlparser_mssql.rs | 2 +- tests/sqlparser_mysql.rs | 12 +++++------ tests/sqlparser_postgres.rs | 12 +++++------ tests/sqlparser_redshift.rs | 2 +- tests/sqlparser_snowflake.rs | 2 +- 13 files changed, 62 insertions(+), 53 deletions(-) create mode 100644 test.sql diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 72254fc34..174da5eba 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1081,16 +1081,16 @@ impl fmt::Display for WindowFrameUnits { #[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 WindowFunctionOption { +pub enum NullTreatment { IgnoreNulls, RespectNulls, } -impl fmt::Display for WindowFunctionOption { +impl fmt::Display for NullTreatment { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match self { - WindowFunctionOption::IgnoreNulls => "IGNORE NULLS", - WindowFunctionOption::RespectNulls => "RESPECT NULLS", + NullTreatment::IgnoreNulls => "IGNORE NULLS", + NullTreatment::RespectNulls => "RESPECT NULLS", }) } } @@ -3667,7 +3667,7 @@ impl fmt::Display for CloseCursor { pub struct Function { pub name: ObjectName, pub args: Vec, - pub nulls_clause: Option, + pub null_treatment: Option, pub over: Option, // aggregate functions may specify eg `COUNT(DISTINCT x)` pub distinct: bool, @@ -3716,7 +3716,7 @@ impl fmt::Display for Function { display_comma_separated(&self.order_by), )?; - if let Some(o) = &self.nulls_clause { + if let Some(o) = &self.null_treatment { write!(f, " {o}")?; } diff --git a/src/ast/visitor.rs b/src/ast/visitor.rs index 00e45372c..11384456f 100644 --- a/src/ast/visitor.rs +++ b/src/ast/visitor.rs @@ -506,7 +506,7 @@ where /// *expr = Expr::Function(Function { /// name: ObjectName(vec![Ident::new("f")]), /// args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(old_expr))], -/// nulls_clause: None, +/// null_treatment: None, /// over: None, distinct: false, special: false, order_by: vec![], /// }); /// } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ba5a91f31..16d5174c4 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -772,7 +772,7 @@ impl<'a> Parser<'a> { Ok(Expr::Function(Function { name: ObjectName(vec![w.to_ident()]), args: vec![], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: true, @@ -958,13 +958,14 @@ impl<'a> Parser<'a> { self.expect_token(&Token::LParen)?; let distinct = self.parse_all_or_distinct()?.is_some(); let (args, order_by) = self.parse_optional_args_with_orderby()?; - let nulls_clause = match self.parse_one_of_keywords(&[Keyword::RESPECT, Keyword::IGNORE]) { + let null_treatment = match self.parse_one_of_keywords(&[Keyword::RESPECT, Keyword::IGNORE]) + { Some(keyword) => { self.expect_keyword(Keyword::NULLS)?; match keyword { - Keyword::RESPECT => Some(WindowFunctionOption::RespectNulls), - Keyword::IGNORE => Some(WindowFunctionOption::IgnoreNulls), + Keyword::RESPECT => Some(NullTreatment::RespectNulls), + Keyword::IGNORE => Some(NullTreatment::IgnoreNulls), _ => None, } } @@ -983,7 +984,7 @@ impl<'a> Parser<'a> { Ok(Expr::Function(Function { name, args, - nulls_clause, + null_treatment, over, distinct, special: false, @@ -1001,7 +1002,7 @@ impl<'a> Parser<'a> { Ok(Expr::Function(Function { name, args, - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special, diff --git a/test.sql b/test.sql new file mode 100644 index 000000000..4f0904e63 --- /dev/null +++ b/test.sql @@ -0,0 +1,8 @@ +SELECT + c1 +FROM + GFC_SOCIAL.SLOT +WHERE + DATE >= DATE(:myvar) -3 + AND DATE < DATE(SYSDATE()) -- SELECT + -- :1 diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index 1ff5d5f50..1b75a5568 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -533,7 +533,7 @@ fn parse_map_access_offset() { args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( number("0") ))),], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index 481c801c9..45421cfb6 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -50,7 +50,7 @@ fn parse_map_access_expr() { Value::SingleQuotedString("endpoint".to_string()) ))), ], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -90,7 +90,7 @@ fn parse_map_access_expr() { Value::SingleQuotedString("app".to_string()) ))), ], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -140,7 +140,7 @@ fn parse_array_fn() { FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Identifier(Ident::new("x1")))), FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Identifier(Ident::new("x2")))), ], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -199,7 +199,7 @@ fn parse_delimited_identifiers() { &Expr::Function(Function { name: ObjectName(vec![Ident::with_quote('"', "myfun")]), args: vec![], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index ed4bf70fd..bd3c02eba 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -875,7 +875,7 @@ fn parse_select_count_wildcard() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("COUNT")]), args: vec![FunctionArg::Unnamed(FunctionArgExpr::Wildcard)], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -896,7 +896,7 @@ fn parse_select_count_distinct() { op: UnaryOperator::Plus, expr: Box::new(Expr::Identifier(Ident::new("x"))), }))], - nulls_clause: None, + null_treatment: None, over: None, distinct: true, special: false, @@ -1864,7 +1864,7 @@ fn parse_select_having() { left: Box::new(Expr::Function(Function { name: ObjectName(vec![Ident::new("COUNT")]), args: vec![FunctionArg::Unnamed(FunctionArgExpr::Wildcard)], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -1890,7 +1890,7 @@ fn parse_select_qualify() { left: Box::new(Expr::Function(Function { name: ObjectName(vec![Ident::new("ROW_NUMBER")]), args: vec![], - nulls_clause: None, + null_treatment: None, over: Some(WindowType::WindowSpec(WindowSpec { partition_by: vec![Expr::Identifier(Ident::new("p"))], order_by: vec![OrderByExpr { @@ -3352,7 +3352,7 @@ fn parse_scalar_function_in_projection() { args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( Expr::Identifier(Ident::new("id")) ))], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -3472,7 +3472,7 @@ fn parse_named_argument_function() { ))), }, ], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -3504,7 +3504,7 @@ fn parse_window_functions() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("row_number")]), args: vec![], - nulls_clause: None, + null_treatment: None, over: Some(WindowType::WindowSpec(WindowSpec { partition_by: vec![], order_by: vec![OrderByExpr { @@ -3548,7 +3548,7 @@ fn test_parse_named_window() { quote_style: None, }), ))], - nulls_clause: None, + null_treatment: None, over: Some(WindowType::NamedWindow(Ident { value: "window1".to_string(), quote_style: None, @@ -3574,7 +3574,7 @@ fn test_parse_named_window() { quote_style: None, }), ))], - nulls_clause: None, + null_treatment: None, over: Some(WindowType::NamedWindow(Ident { value: "window2".to_string(), quote_style: None, @@ -4044,7 +4044,7 @@ fn parse_at_timezone() { quote_style: None, }]), args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(zero.clone()))], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -4072,7 +4072,7 @@ fn parse_at_timezone() { quote_style: None, },],), args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(zero))], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -4084,7 +4084,7 @@ fn parse_at_timezone() { Value::SingleQuotedString("%Y-%m-%dT%H".to_string()), ),),), ], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -4243,7 +4243,7 @@ fn parse_table_function() { args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( Value::SingleQuotedString("1".to_owned()), )))], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -4395,7 +4395,7 @@ fn parse_unnest_in_from_clause() { FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("2")))), FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("3")))), ], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -4425,7 +4425,7 @@ fn parse_unnest_in_from_clause() { FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("2")))), FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("3")))), ], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -4437,7 +4437,7 @@ fn parse_unnest_in_from_clause() { FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("5")))), FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("6")))), ], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -6910,7 +6910,7 @@ fn parse_time_functions() { let select_localtime_func_call_ast = Function { name: ObjectName(vec![Ident::new(func_name)]), args: vec![], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -7397,7 +7397,7 @@ fn parse_pivot_table() { args: (vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( Expr::CompoundIdentifier(vec![Ident::new("a"), Ident::new("amount"),]) ))]), - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -7547,7 +7547,7 @@ fn parse_pivot_unpivot_table() { args: (vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( Expr::Identifier(Ident::new("population")) ))]), - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs index a26f3d24d..dd55d065d 100644 --- a/tests/sqlparser_hive.rs +++ b/tests/sqlparser_hive.rs @@ -346,7 +346,7 @@ fn parse_delimited_identifiers() { &Expr::Function(Function { name: ObjectName(vec![Ident::with_quote('"', "myfun")]), args: vec![], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index de701d2e5..3d53500d8 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -334,7 +334,7 @@ fn parse_delimited_identifiers() { &Expr::Function(Function { name: ObjectName(vec![Ident::with_quote('"', "myfun")]), args: vec![], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 52b56c6bd..6f07bbbfe 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -1071,7 +1071,7 @@ fn parse_insert_with_on_duplicate_update() { args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( Expr::Identifier(Ident::new("description")) ))], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -1085,7 +1085,7 @@ fn parse_insert_with_on_duplicate_update() { args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( Expr::Identifier(Ident::new("perm_create")) ))], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -1099,7 +1099,7 @@ fn parse_insert_with_on_duplicate_update() { args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( Expr::Identifier(Ident::new("perm_read")) ))], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -1113,7 +1113,7 @@ fn parse_insert_with_on_duplicate_update() { args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( Expr::Identifier(Ident::new("perm_update")) ))], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -1127,7 +1127,7 @@ fn parse_insert_with_on_duplicate_update() { args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( Expr::Identifier(Ident::new("perm_delete")) ))], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -1505,7 +1505,7 @@ fn parse_table_colum_option_on_update() { option: ColumnOption::OnUpdate(Expr::Function(Function { name: ObjectName(vec![Ident::new("CURRENT_TIMESTAMP")]), args: vec![], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 3d0f8cee2..885f9d125 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -2274,7 +2274,7 @@ fn test_composite_value() { named: true } )))], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, @@ -2436,7 +2436,7 @@ fn parse_current_functions() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("CURRENT_CATALOG")]), args: vec![], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: true, @@ -2448,7 +2448,7 @@ fn parse_current_functions() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("CURRENT_USER")]), args: vec![], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: true, @@ -2460,7 +2460,7 @@ fn parse_current_functions() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("SESSION_USER")]), args: vec![], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: true, @@ -2472,7 +2472,7 @@ fn parse_current_functions() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("USER")]), args: vec![], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: true, @@ -2923,7 +2923,7 @@ fn parse_delimited_identifiers() { &Expr::Function(Function { name: ObjectName(vec![Ident::with_quote('"', "myfun")]), args: vec![], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index 50dcd07d8..10ae04559 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs @@ -137,7 +137,7 @@ fn parse_delimited_identifiers() { &Expr::Function(Function { name: ObjectName(vec![Ident::with_quote('"', "myfun")]), args: vec![], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 8c5120e4d..257cbb6c6 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -247,7 +247,7 @@ fn parse_delimited_identifiers() { &Expr::Function(Function { name: ObjectName(vec![Ident::with_quote('"', "myfun")]), args: vec![], - nulls_clause: None, + null_treatment: None, over: None, distinct: false, special: false, From e5b30e51de462bc2923d384c0761895a58cad2da Mon Sep 17 00:00:00 2001 From: Yuval Shkolar Date: Sun, 22 Oct 2023 10:47:14 +0300 Subject: [PATCH 10/13] delete file --- test.sql | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 test.sql diff --git a/test.sql b/test.sql deleted file mode 100644 index 4f0904e63..000000000 --- a/test.sql +++ /dev/null @@ -1,8 +0,0 @@ -SELECT - c1 -FROM - GFC_SOCIAL.SLOT -WHERE - DATE >= DATE(:myvar) -3 - AND DATE < DATE(SYSDATE()) -- SELECT - -- :1 From 504a215828f10101b1cad60f7af449cc40e1f1dd Mon Sep 17 00:00:00 2001 From: Yuval Shkolar Date: Sun, 22 Oct 2023 11:12:56 +0300 Subject: [PATCH 11/13] PR --- src/ast/mod.rs | 2 ++ tests/sqlparser_snowflake.rs | 22 ---------------------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 174da5eba..8b147b0f1 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1078,6 +1078,7 @@ impl fmt::Display for WindowFrameUnits { } } +/// Specifies Ignore / Respect NULL wihtin rank functions #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] @@ -3667,6 +3668,7 @@ impl fmt::Display for CloseCursor { pub struct Function { pub name: ObjectName, pub args: Vec, + // Snowflake/MSSQL supports diffrent options for null treatment in rank functions pub null_treatment: Option, pub over: Option, // aggregate functions may specify eg `COUNT(DISTINCT x)` diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 257cbb6c6..511c1f441 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -1065,25 +1065,3 @@ fn test_snowflake_trim() { snowflake().parse_sql_statements(error_sql).unwrap_err() ); } - -#[test] -fn parse_agg_with_order_by() { - for sql in [ - "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)", - ] { - snowflake().verified_stmt(sql); - } - - 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", - ] { - snowflake().verified_stmt(sql); - } -} From b7b9b30addfe26f178536d9a601e34f15d23a57f Mon Sep 17 00:00:00 2001 From: yuval-illumex <85674443+yuval-illumex@users.noreply.github.com> Date: Tue, 24 Oct 2023 14:33:48 +0300 Subject: [PATCH 12/13] Update src/ast/mod.rs Co-authored-by: Andrew Lamb --- src/ast/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 8b147b0f1..14926c313 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1078,7 +1078,9 @@ impl fmt::Display for WindowFrameUnits { } } -/// Specifies Ignore / Respect NULL wihtin rank functions +/// 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))] From 6432c944ccb8b971ab261694045a7948e1dff7ae Mon Sep 17 00:00:00 2001 From: Yuval Shkolar Date: Tue, 24 Oct 2023 14:49:21 +0300 Subject: [PATCH 13/13] maintain old tests --- tests/sqlparser_common.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index a6d4b2809..5eb70b09b 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2270,6 +2270,29 @@ fn parse_array_agg_func() { #[test] fn parse_agg_with_order_by() { + let supported_dialects = TestedDialects { + dialects: vec![ + Box::new(GenericDialect {}), + Box::new(PostgreSqlDialect {}), + Box::new(MsSqlDialect {}), + Box::new(AnsiDialect {}), + Box::new(HiveDialect {}), + ], + options: None, + }; + + for sql in [ + "SELECT FIRST_VALUE(x ORDER BY x) AS a FROM T", + "SELECT FIRST_VALUE(x ORDER BY x) FROM tbl", + "SELECT LAST_VALUE(x ORDER BY x, y) AS a FROM T", + "SELECT LAST_VALUE(x ORDER BY x ASC, y DESC) AS a FROM T", + ] { + supported_dialects.verified_stmt(sql); + } +} + +#[test] +fn parse_window_rank_function() { let supported_dialects = TestedDialects { dialects: vec![ Box::new(GenericDialect {}),