From 020f9228dde5de15ddbdb9fa0fb149d8012c204a Mon Sep 17 00:00:00 2001 From: Denys Tsomenko Date: Thu, 19 Jun 2025 16:07:01 +0300 Subject: [PATCH 1/4] fix: parse snowflake fetch clause --- src/parser/mod.rs | 19 +++++++++++++++++++ tests/sqlparser_snowflake.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 44bf58d0f..03928fa81 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15017,6 +15017,9 @@ impl<'a> Parser<'a> { /// Parse a FETCH clause pub fn parse_fetch(&mut self) -> Result { + if dialect_of!(self is SnowflakeDialect) { + return self.parse_snowflake_fetch(); + } self.expect_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT])?; let (quantity, percent) = if self .parse_one_of_keywords(&[Keyword::ROW, Keyword::ROWS]) @@ -15043,6 +15046,22 @@ impl<'a> Parser<'a> { }) } + /// Parse a FETCH clause with Snowflake-specific syntax + fn parse_snowflake_fetch(&mut self) -> Result { + // Snowflake: All additional keywords are optional. WITH TIES is not allowed. + let _ = self.parse_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT]); + + let quantity = Expr::Value(self.parse_value()?); + let _ = self.parse_one_of_keywords(&[Keyword::ROW, Keyword::ROWS]); + let _ = self.parse_keyword(Keyword::ONLY); + + Ok(Fetch { + with_ties: false, + percent: false, + quantity: Some(quantity), + }) + } + /// Parse a FOR UPDATE/FOR SHARE clause pub fn parse_lock(&mut self) -> Result { let lock_type = match self.expect_one_of_keywords(&[Keyword::UPDATE, Keyword::SHARE])? { diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index b11a2cb05..a100b6619 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -4146,3 +4146,36 @@ END assert_eq!(2, exception[1].idents.len()); assert_eq!(2, exception[1].statements.len()); } + +#[test] +fn test_snowflake_fetch_clause_syntax() { + let canonical = "SELECT c1 FROM fetch_test FETCH FIRST 2 ROWS ONLY"; + snowflake().verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH 2", canonical); + + snowflake() + .verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH FIRST 2", canonical); + snowflake() + .verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH NEXT 2", canonical); + + snowflake() + .verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH 2 ROW", canonical); + + snowflake().verified_only_select_with_canonical( + "SELECT c1 FROM fetch_test FETCH FIRST 2 ROWS", + canonical, + ); + + snowflake() + .verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH 2 ONLY", canonical); + let res = snowflake().parse_sql_statements("SELECT c1 FROM fetch_test FETCH 2 PERCENT"); + assert_eq!( + res.unwrap_err().to_string(), + "sql parser error: Expected: end of statement, found: PERCENT" + ); + let res = + snowflake().parse_sql_statements("SELECT c1 FROM fetch_test FETCH FIRST 2 ROWS WITH TIES"); + assert_eq!( + res.unwrap_err().to_string(), + "sql parser error: Expected: end of statement, found: WITH" + ); +} From 50ee50a676588f424ef4210f348d9dc591d53021 Mon Sep 17 00:00:00 2001 From: Denys Tsomenko Date: Thu, 19 Jun 2025 16:23:41 +0300 Subject: [PATCH 2/4] Fix tests --- tests/sqlparser_common.rs | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 52054604d..e9d1468a7 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -8369,16 +8369,18 @@ fn parse_offset() { #[test] fn parse_fetch() { + let dialects = all_dialects_except(|d| d.is::()); + let fetch_first_two_rows_only = Some(Fetch { with_ties: false, percent: false, quantity: Some(Expr::value(number("2"))), }); - let ast = verified_query("SELECT foo FROM bar FETCH FIRST 2 ROWS ONLY"); + let ast = dialects.verified_query("SELECT foo FROM bar FETCH FIRST 2 ROWS ONLY"); assert_eq!(ast.fetch, fetch_first_two_rows_only); - let ast = verified_query("SELECT 'foo' FETCH FIRST 2 ROWS ONLY"); + let ast = dialects.verified_query("SELECT 'foo' FETCH FIRST 2 ROWS ONLY"); assert_eq!(ast.fetch, fetch_first_two_rows_only); - let ast = verified_query("SELECT foo FROM bar FETCH FIRST ROWS ONLY"); + let ast = dialects.verified_query("SELECT foo FROM bar FETCH FIRST ROWS ONLY"); assert_eq!( ast.fetch, Some(Fetch { @@ -8387,11 +8389,11 @@ fn parse_fetch() { quantity: None, }) ); - let ast = verified_query("SELECT foo FROM bar WHERE foo = 4 FETCH FIRST 2 ROWS ONLY"); + let ast = dialects.verified_query("SELECT foo FROM bar WHERE foo = 4 FETCH FIRST 2 ROWS ONLY"); assert_eq!(ast.fetch, fetch_first_two_rows_only); - let ast = verified_query("SELECT foo FROM bar ORDER BY baz FETCH FIRST 2 ROWS ONLY"); + let ast = dialects.verified_query("SELECT foo FROM bar ORDER BY baz FETCH FIRST 2 ROWS ONLY"); assert_eq!(ast.fetch, fetch_first_two_rows_only); - let ast = verified_query( + let ast = dialects.verified_query( "SELECT foo FROM bar WHERE foo = 4 ORDER BY baz FETCH FIRST 2 ROWS WITH TIES", ); assert_eq!( @@ -8402,7 +8404,7 @@ fn parse_fetch() { quantity: Some(Expr::value(number("2"))), }) ); - let ast = verified_query("SELECT foo FROM bar FETCH FIRST 50 PERCENT ROWS ONLY"); + let ast = dialects.verified_query("SELECT foo FROM bar FETCH FIRST 50 PERCENT ROWS ONLY"); assert_eq!( ast.fetch, Some(Fetch { @@ -8411,7 +8413,7 @@ fn parse_fetch() { quantity: Some(Expr::value(number("50"))), }) ); - let ast = verified_query( + let ast = dialects.verified_query( "SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY", ); let expected_limit_clause = Some(LimitClause::LimitOffset { @@ -8424,7 +8426,7 @@ fn parse_fetch() { }); assert_eq!(ast.limit_clause, expected_limit_clause); assert_eq!(ast.fetch, fetch_first_two_rows_only); - let ast = verified_query( + let ast = dialects.verified_query( "SELECT foo FROM (SELECT * FROM bar FETCH FIRST 2 ROWS ONLY) FETCH FIRST 2 ROWS ONLY", ); assert_eq!(ast.fetch, fetch_first_two_rows_only); @@ -8437,7 +8439,7 @@ fn parse_fetch() { }, _ => panic!("Test broke"), } - let ast = verified_query("SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY) OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY"); + let ast = dialects.verified_query("SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY) OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY"); let expected_limit_clause = &Some(LimitClause::LimitOffset { limit: None, offset: Some(Offset { @@ -8462,23 +8464,24 @@ fn parse_fetch() { #[test] fn parse_fetch_variations() { - one_statement_parses_to( + let dialects = all_dialects_except(|d| d.is::()); + dialects.one_statement_parses_to( "SELECT foo FROM bar FETCH FIRST 10 ROW ONLY", "SELECT foo FROM bar FETCH FIRST 10 ROWS ONLY", ); - one_statement_parses_to( + dialects.one_statement_parses_to( "SELECT foo FROM bar FETCH NEXT 10 ROW ONLY", "SELECT foo FROM bar FETCH FIRST 10 ROWS ONLY", ); - one_statement_parses_to( + dialects.one_statement_parses_to( "SELECT foo FROM bar FETCH NEXT 10 ROWS WITH TIES", "SELECT foo FROM bar FETCH FIRST 10 ROWS WITH TIES", ); - one_statement_parses_to( + dialects.one_statement_parses_to( "SELECT foo FROM bar FETCH NEXT ROWS WITH TIES", "SELECT foo FROM bar FETCH FIRST ROWS WITH TIES", ); - one_statement_parses_to( + dialects.one_statement_parses_to( "SELECT foo FROM bar FETCH FIRST ROWS ONLY", "SELECT foo FROM bar FETCH FIRST ROWS ONLY", ); From 68d8d871cd34ad58df50af91b93b05d188085e54 Mon Sep 17 00:00:00 2001 From: Denys Tsomenko Date: Mon, 23 Jun 2025 16:11:41 +0300 Subject: [PATCH 3/4] Make parse_fetch more permissive --- src/parser/mod.rs | 30 ++++++------------------------ tests/sqlparser_snowflake.rs | 14 -------------- 2 files changed, 6 insertions(+), 38 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 03928fa81..73a68dc37 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15017,10 +15017,8 @@ impl<'a> Parser<'a> { /// Parse a FETCH clause pub fn parse_fetch(&mut self) -> Result { - if dialect_of!(self is SnowflakeDialect) { - return self.parse_snowflake_fetch(); - } - self.expect_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT])?; + let _ = self.parse_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT]); + let (quantity, percent) = if self .parse_one_of_keywords(&[Keyword::ROW, Keyword::ROWS]) .is_some() @@ -15029,16 +15027,16 @@ impl<'a> Parser<'a> { } else { let quantity = Expr::Value(self.parse_value()?); let percent = self.parse_keyword(Keyword::PERCENT); - self.expect_one_of_keywords(&[Keyword::ROW, Keyword::ROWS])?; + let _ = self.parse_one_of_keywords(&[Keyword::ROW, Keyword::ROWS]); (Some(quantity), percent) }; + let with_ties = if self.parse_keyword(Keyword::ONLY) { false - } else if self.parse_keywords(&[Keyword::WITH, Keyword::TIES]) { - true } else { - return self.expected("one of ONLY or WITH TIES", self.peek_token()); + self.parse_keywords(&[Keyword::WITH, Keyword::TIES]) }; + Ok(Fetch { with_ties, percent, @@ -15046,22 +15044,6 @@ impl<'a> Parser<'a> { }) } - /// Parse a FETCH clause with Snowflake-specific syntax - fn parse_snowflake_fetch(&mut self) -> Result { - // Snowflake: All additional keywords are optional. WITH TIES is not allowed. - let _ = self.parse_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT]); - - let quantity = Expr::Value(self.parse_value()?); - let _ = self.parse_one_of_keywords(&[Keyword::ROW, Keyword::ROWS]); - let _ = self.parse_keyword(Keyword::ONLY); - - Ok(Fetch { - with_ties: false, - percent: false, - quantity: Some(quantity), - }) - } - /// Parse a FOR UPDATE/FOR SHARE clause pub fn parse_lock(&mut self) -> Result { let lock_type = match self.expect_one_of_keywords(&[Keyword::UPDATE, Keyword::SHARE])? { diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index a100b6619..7dc00f9a8 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -4164,18 +4164,4 @@ fn test_snowflake_fetch_clause_syntax() { "SELECT c1 FROM fetch_test FETCH FIRST 2 ROWS", canonical, ); - - snowflake() - .verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH 2 ONLY", canonical); - let res = snowflake().parse_sql_statements("SELECT c1 FROM fetch_test FETCH 2 PERCENT"); - assert_eq!( - res.unwrap_err().to_string(), - "sql parser error: Expected: end of statement, found: PERCENT" - ); - let res = - snowflake().parse_sql_statements("SELECT c1 FROM fetch_test FETCH FIRST 2 ROWS WITH TIES"); - assert_eq!( - res.unwrap_err().to_string(), - "sql parser error: Expected: end of statement, found: WITH" - ); } From 6062c0695602ce079e80afaaeab5a1dd0ebb7a19 Mon Sep 17 00:00:00 2001 From: Denys Tsomenko Date: Mon, 23 Jun 2025 16:15:49 +0300 Subject: [PATCH 4/4] Revert "Fix tests" This reverts commit 50ee50a676588f424ef4210f348d9dc591d53021. --- tests/sqlparser_common.rs | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index e9d1468a7..52054604d 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -8369,18 +8369,16 @@ fn parse_offset() { #[test] fn parse_fetch() { - let dialects = all_dialects_except(|d| d.is::()); - let fetch_first_two_rows_only = Some(Fetch { with_ties: false, percent: false, quantity: Some(Expr::value(number("2"))), }); - let ast = dialects.verified_query("SELECT foo FROM bar FETCH FIRST 2 ROWS ONLY"); + let ast = verified_query("SELECT foo FROM bar FETCH FIRST 2 ROWS ONLY"); assert_eq!(ast.fetch, fetch_first_two_rows_only); - let ast = dialects.verified_query("SELECT 'foo' FETCH FIRST 2 ROWS ONLY"); + let ast = verified_query("SELECT 'foo' FETCH FIRST 2 ROWS ONLY"); assert_eq!(ast.fetch, fetch_first_two_rows_only); - let ast = dialects.verified_query("SELECT foo FROM bar FETCH FIRST ROWS ONLY"); + let ast = verified_query("SELECT foo FROM bar FETCH FIRST ROWS ONLY"); assert_eq!( ast.fetch, Some(Fetch { @@ -8389,11 +8387,11 @@ fn parse_fetch() { quantity: None, }) ); - let ast = dialects.verified_query("SELECT foo FROM bar WHERE foo = 4 FETCH FIRST 2 ROWS ONLY"); + let ast = verified_query("SELECT foo FROM bar WHERE foo = 4 FETCH FIRST 2 ROWS ONLY"); assert_eq!(ast.fetch, fetch_first_two_rows_only); - let ast = dialects.verified_query("SELECT foo FROM bar ORDER BY baz FETCH FIRST 2 ROWS ONLY"); + let ast = verified_query("SELECT foo FROM bar ORDER BY baz FETCH FIRST 2 ROWS ONLY"); assert_eq!(ast.fetch, fetch_first_two_rows_only); - let ast = dialects.verified_query( + let ast = verified_query( "SELECT foo FROM bar WHERE foo = 4 ORDER BY baz FETCH FIRST 2 ROWS WITH TIES", ); assert_eq!( @@ -8404,7 +8402,7 @@ fn parse_fetch() { quantity: Some(Expr::value(number("2"))), }) ); - let ast = dialects.verified_query("SELECT foo FROM bar FETCH FIRST 50 PERCENT ROWS ONLY"); + let ast = verified_query("SELECT foo FROM bar FETCH FIRST 50 PERCENT ROWS ONLY"); assert_eq!( ast.fetch, Some(Fetch { @@ -8413,7 +8411,7 @@ fn parse_fetch() { quantity: Some(Expr::value(number("50"))), }) ); - let ast = dialects.verified_query( + let ast = verified_query( "SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY", ); let expected_limit_clause = Some(LimitClause::LimitOffset { @@ -8426,7 +8424,7 @@ fn parse_fetch() { }); assert_eq!(ast.limit_clause, expected_limit_clause); assert_eq!(ast.fetch, fetch_first_two_rows_only); - let ast = dialects.verified_query( + let ast = verified_query( "SELECT foo FROM (SELECT * FROM bar FETCH FIRST 2 ROWS ONLY) FETCH FIRST 2 ROWS ONLY", ); assert_eq!(ast.fetch, fetch_first_two_rows_only); @@ -8439,7 +8437,7 @@ fn parse_fetch() { }, _ => panic!("Test broke"), } - let ast = dialects.verified_query("SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY) OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY"); + let ast = verified_query("SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY) OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY"); let expected_limit_clause = &Some(LimitClause::LimitOffset { limit: None, offset: Some(Offset { @@ -8464,24 +8462,23 @@ fn parse_fetch() { #[test] fn parse_fetch_variations() { - let dialects = all_dialects_except(|d| d.is::()); - dialects.one_statement_parses_to( + one_statement_parses_to( "SELECT foo FROM bar FETCH FIRST 10 ROW ONLY", "SELECT foo FROM bar FETCH FIRST 10 ROWS ONLY", ); - dialects.one_statement_parses_to( + one_statement_parses_to( "SELECT foo FROM bar FETCH NEXT 10 ROW ONLY", "SELECT foo FROM bar FETCH FIRST 10 ROWS ONLY", ); - dialects.one_statement_parses_to( + one_statement_parses_to( "SELECT foo FROM bar FETCH NEXT 10 ROWS WITH TIES", "SELECT foo FROM bar FETCH FIRST 10 ROWS WITH TIES", ); - dialects.one_statement_parses_to( + one_statement_parses_to( "SELECT foo FROM bar FETCH NEXT ROWS WITH TIES", "SELECT foo FROM bar FETCH FIRST ROWS WITH TIES", ); - dialects.one_statement_parses_to( + one_statement_parses_to( "SELECT foo FROM bar FETCH FIRST ROWS ONLY", "SELECT foo FROM bar FETCH FIRST ROWS ONLY", );