From 9ea9cb10cfdb34d248ee65ba688f89e2c19a5ca3 Mon Sep 17 00:00:00 2001 From: Ayman Elkfrawy Date: Wed, 20 Nov 2024 13:05:13 -0800 Subject: [PATCH 1/3] support snowflake double dot notation for object name --- src/dialect/mod.rs | 10 ++++++++++ src/dialect/snowflake.rs | 8 ++++++++ src/parser/mod.rs | 7 +++++++ tests/sqlparser_snowflake.rs | 29 +++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+) diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 159e14717..189292bc2 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -365,6 +365,16 @@ pub trait Dialect: Debug + Any { self.supports_trailing_commas() } + /// Returns true if the dialect supports double dot notation for object names + /// + /// Example + /// ```sql + /// SELECT * FROM db_name..table_name + /// ``` + fn supports_object_name_double_dot_notation(&self) -> bool { + false + } + /// Dialect-specific infix parser override /// /// This method is called to parse the next infix expression. diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index b584ed9b4..0db6ee3c8 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -52,6 +52,14 @@ impl Dialect for SnowflakeDialect { true } + // Snowflake supports double-dot notation when the schema name is not specified + // In this case the default PUBLIC schema is used + // + // see https://docs.snowflake.com/en/sql-reference/name-resolution#resolution-when-schema-omitted-double-dot-notation + fn supports_object_name_double_dot_notation(&self) -> bool { + true + } + fn is_identifier_part(&self, ch: char) -> bool { ch.is_ascii_lowercase() || ch.is_ascii_uppercase() diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 1bf173169..2c6799c73 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8382,6 +8382,13 @@ impl<'a> Parser<'a> { pub fn parse_object_name(&mut self, in_table_clause: bool) -> Result { let mut idents = vec![]; loop { + if self.dialect.supports_object_name_double_dot_notation() + && !idents.is_empty() + && self.peek_token() == Token::Period + { + self.next_token(); + idents.push(Ident::new("")); + } idents.push(self.parse_identifier(in_table_clause)?); if !self.consume_token(&Token::Period) { break; diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index f99a00f5b..6aff002c6 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -2864,3 +2864,32 @@ fn test_projection_with_nested_trailing_commas() { let sql = "SELECT a, b, FROM c, (SELECT d, e, FROM f, LATERAL FLATTEN(input => events))"; let _ = snowflake().parse_sql_statements(sql).unwrap(); } + +#[test] +fn test_sf_double_dot_notation() { + snowflake().verified_stmt("SELECT * FROM db_name..table_name"); + snowflake().verified_stmt("SELECT * FROM x, y..z JOIN a..b as as b ON x.id = b.id"); +} + +#[test] +fn test_sf_double_dot_notation_wrong_position() {} + +#[test] +fn test_parse_double_dot_notation_wrong_position() { + assert_eq!( + snowflake() + .parse_sql_statements("SELECT * FROM X.Y..") + .unwrap_err() + .to_string(), + "sql parser error: Expected: identifier, found: ." + ); + + assert_eq!( + // Ensure we don't parse leading token + snowflake() + .parse_sql_statements("SELECT * FROM .X.Y") + .unwrap_err() + .to_string(), + "sql parser error: Expected: identifier, found: ." + ); +} From c603785aab72c3f9699dbc7243eb759863d6fcf7 Mon Sep 17 00:00:00 2001 From: Ayman Elkfrawy Date: Thu, 21 Nov 2024 12:38:45 -0800 Subject: [PATCH 2/3] fix test case --- src/parser/mod.rs | 2 +- tests/sqlparser_snowflake.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 2c6799c73..ad43acbb5 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8383,7 +8383,7 @@ impl<'a> Parser<'a> { let mut idents = vec![]; loop { if self.dialect.supports_object_name_double_dot_notation() - && !idents.is_empty() + && idents.len() == 1 && self.peek_token() == Token::Period { self.next_token(); diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 6aff002c6..064b4b1e4 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -2868,7 +2868,7 @@ fn test_projection_with_nested_trailing_commas() { #[test] fn test_sf_double_dot_notation() { snowflake().verified_stmt("SELECT * FROM db_name..table_name"); - snowflake().verified_stmt("SELECT * FROM x, y..z JOIN a..b as as b ON x.id = b.id"); + snowflake().verified_stmt("SELECT * FROM x, y..z JOIN a..b AS b ON x.id = b.id"); } #[test] From 6a26aa478776eea9cd1e17bf2402d601bf65375c Mon Sep 17 00:00:00 2001 From: Ayman Elkfrawy Date: Sun, 24 Nov 2024 12:52:15 -0800 Subject: [PATCH 3/3] address comments --- src/parser/mod.rs | 4 ++-- tests/sqlparser_snowflake.rs | 17 ++++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ad43acbb5..d91576016 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8384,9 +8384,9 @@ impl<'a> Parser<'a> { loop { if self.dialect.supports_object_name_double_dot_notation() && idents.len() == 1 - && self.peek_token() == Token::Period + && self.consume_token(&Token::Period) { - self.next_token(); + // Empty string here means default schema idents.push(Ident::new("")); } idents.push(self.parse_identifier(in_table_clause)?); diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 064b4b1e4..d7225eb7e 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -2869,13 +2869,7 @@ fn test_projection_with_nested_trailing_commas() { fn test_sf_double_dot_notation() { snowflake().verified_stmt("SELECT * FROM db_name..table_name"); snowflake().verified_stmt("SELECT * FROM x, y..z JOIN a..b AS b ON x.id = b.id"); -} - -#[test] -fn test_sf_double_dot_notation_wrong_position() {} -#[test] -fn test_parse_double_dot_notation_wrong_position() { assert_eq!( snowflake() .parse_sql_statements("SELECT * FROM X.Y..") @@ -2883,7 +2877,13 @@ fn test_parse_double_dot_notation_wrong_position() { .to_string(), "sql parser error: Expected: identifier, found: ." ); - + assert_eq!( + snowflake() + .parse_sql_statements("SELECT * FROM X..Y..Z") + .unwrap_err() + .to_string(), + "sql parser error: Expected: identifier, found: ." + ); assert_eq!( // Ensure we don't parse leading token snowflake() @@ -2893,3 +2893,6 @@ fn test_parse_double_dot_notation_wrong_position() { "sql parser error: Expected: identifier, found: ." ); } + +#[test] +fn test_parse_double_dot_notation_wrong_position() {}