From 984d805dffd896a89bc6d7562d3c6692816e6b87 Mon Sep 17 00:00:00 2001 From: Kacper Muda Date: Wed, 21 Aug 2024 12:14:29 +0200 Subject: [PATCH 1/2] feat: support different USE statement syntaxes --- src/ast/mod.rs | 32 +++++++++++++++---- src/keywords.rs | 1 + src/parser/mod.rs | 59 ++++++++++++++++++++++++++++++++-- tests/sqlparser_clickhouse.rs | 36 +++++++++++++++++++++ tests/sqlparser_databricks.rs | 52 ++++++++++++++++++++++++++++++ tests/sqlparser_duckdb.rs | 52 ++++++++++++++++++++++++++++++ tests/sqlparser_hive.rs | 44 +++++++++++++++++++++++++ tests/sqlparser_mssql.rs | 36 +++++++++++++++++++++ tests/sqlparser_mysql.rs | 28 +++++++++++++++- tests/sqlparser_snowflake.rs | 60 +++++++++++++++++++++++++++++++++++ 10 files changed, 391 insertions(+), 9 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index c4533ef57..89926a2b0 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2515,11 +2515,13 @@ pub enum Statement { /// Note: this is a MySQL-specific statement. ShowCollation { filter: Option }, /// ```sql - /// USE + /// USE [DATABASE|SCHEMA|CATALOG|...] [.||] /// ``` - /// - /// Note: This is a MySQL-specific statement. - Use { db_name: Ident }, + Use { + db_name: Option, + schema_name: Option, + keyword: Option, + }, /// ```sql /// START [ TRANSACTION | WORK ] | START TRANSACTION } ... /// ``` @@ -4125,8 +4127,26 @@ impl fmt::Display for Statement { } Ok(()) } - Statement::Use { db_name } => { - write!(f, "USE {db_name}")?; + Statement::Use { + db_name, + schema_name, + keyword, + } => { + write!(f, "USE")?; + + if let Some(kw) = keyword.as_ref() { + write!(f, " {}", kw)?; + } + + if let Some(db_name) = db_name { + write!(f, " {}", db_name)?; + if let Some(schema_name) = schema_name { + write!(f, ".{}", schema_name)?; + } + } else if let Some(schema_name) = schema_name { + write!(f, " {}", schema_name)?; + } + Ok(()) } Statement::ShowCollation { filter } => { diff --git a/src/keywords.rs b/src/keywords.rs index 538c2d380..4fe65ebca 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -137,6 +137,7 @@ define_keywords!( CASCADED, CASE, CAST, + CATALOG, CEIL, CEILING, CENTURY, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 2632d807a..28e727ff5 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -9225,8 +9225,63 @@ impl<'a> Parser<'a> { } pub fn parse_use(&mut self) -> Result { - let db_name = self.parse_identifier(false)?; - Ok(Statement::Use { db_name }) + // What should be treated as keyword in given dialect + let allowed_keywords = if dialect_of!(self is HiveDialect) { + vec![Keyword::DEFAULT] + } else if dialect_of!(self is DatabricksDialect) { + vec![Keyword::CATALOG, Keyword::DATABASE, Keyword::SCHEMA] + } else if dialect_of!(self is SnowflakeDialect) { + vec![Keyword::DATABASE, Keyword::SCHEMA] + } else { + vec![] + }; + let parsed_keyword = self.parse_one_of_keywords(&allowed_keywords); + + // Hive dialect accepts USE DEFAULT; statement without any db specified + if dialect_of!(self is HiveDialect) && parsed_keyword == Some(Keyword::DEFAULT) { + return Ok(Statement::Use { + db_name: None, + schema_name: None, + keyword: Some("DEFAULT".to_string()), + }); + } + + // Parse the object name, which might be a single identifier or fully qualified name (e.g., x.y) + let parts = self.parse_object_name(false)?.0; + let (db_name, schema_name) = match parts.len() { + 1 => { + // Single identifier found + if dialect_of!(self is DatabricksDialect) { + if parsed_keyword == Some(Keyword::CATALOG) { + // Databricks: CATALOG keyword provided, treat as database name + (Some(parts[0].clone()), None) + } else { + // Databricks: DATABASE, SCHEMA or no keyword provided, treat as schema name + (None, Some(parts[0].clone())) + } + } else if dialect_of!(self is SnowflakeDialect) + && parsed_keyword == Some(Keyword::SCHEMA) + { + // Snowflake: SCHEMA keyword provided, treat as schema name + (None, Some(parts[0].clone())) + } else { + // Other dialects: treat as database name by default + (Some(parts[0].clone()), None) + } + } + 2 => (Some(parts[0].clone()), Some(parts[1].clone())), + _ => { + return Err(ParserError::ParserError( + "Invalid format in the USE statement".to_string(), + )) + } + }; + + Ok(Statement::Use { + db_name, + schema_name, + keyword: parsed_keyword.map(|kw| format!("{:?}", kw)), + }) } pub fn parse_table_and_joins(&mut self) -> Result { diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index 931899ff9..52c9fa1d2 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -1160,6 +1160,42 @@ fn test_prewhere() { } } +#[test] +fn parse_use() { + assert_eq!( + clickhouse().verified_stmt("USE mydb"), + Statement::Use { + db_name: Some(Ident::new("mydb")), + schema_name: None, + keyword: None + } + ); + assert_eq!( + clickhouse().verified_stmt("USE DATABASE"), + Statement::Use { + db_name: Some(Ident::new("DATABASE")), + schema_name: None, + keyword: None + } + ); + assert_eq!( + clickhouse().verified_stmt("USE SCHEMA"), + Statement::Use { + db_name: Some(Ident::new("SCHEMA")), + schema_name: None, + keyword: None + } + ); + assert_eq!( + clickhouse().verified_stmt("USE CATALOG"), + Statement::Use { + db_name: Some(Ident::new("CATALOG")), + schema_name: None, + keyword: None + } + ); +} + #[test] fn test_query_with_format_clause() { let format_options = vec!["TabSeparated", "JSONCompact", "NULL"]; diff --git a/tests/sqlparser_databricks.rs b/tests/sqlparser_databricks.rs index 280b97b49..9c3757090 100644 --- a/tests/sqlparser_databricks.rs +++ b/tests/sqlparser_databricks.rs @@ -189,3 +189,55 @@ fn test_values_clause() { // TODO: support this example from https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-values.html#examples // databricks().verified_query("VALUES 1, 2, 3"); } + +#[test] +fn parse_use() { + assert_eq!( + databricks().verified_stmt("USE my_schema"), + Statement::Use { + db_name: None, + schema_name: Some(Ident::new("my_schema")), + keyword: None + } + ); + assert_eq!( + databricks().verified_stmt("USE CATALOG my_catalog"), + Statement::Use { + db_name: Some(Ident::new("my_catalog")), + schema_name: None, + keyword: Some("CATALOG".to_string()) + } + ); + assert_eq!( + databricks().verified_stmt("USE CATALOG 'my_catalog'"), + Statement::Use { + db_name: Some(Ident::with_quote('\'', "my_catalog")), + schema_name: None, + keyword: Some("CATALOG".to_string()) + } + ); + assert_eq!( + databricks().verified_stmt("USE DATABASE my_schema"), + Statement::Use { + db_name: None, + schema_name: Some(Ident::new("my_schema")), + keyword: Some("DATABASE".to_string()) + } + ); + assert_eq!( + databricks().verified_stmt("USE SCHEMA my_schema"), + Statement::Use { + db_name: None, + schema_name: Some(Ident::new("my_schema")), + keyword: Some("SCHEMA".to_string()) + } + ); + + let invalid_cases = ["USE SCHEMA", "USE DATABASE", "USE CATALOG"]; + for sql in &invalid_cases { + assert_eq!( + databricks().parse_sql_statements(sql).unwrap_err(), + ParserError::ParserError("Expected: identifier, found: EOF".to_string()), + ); + } +} diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index 6e6c4e230..8702a663b 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -756,3 +756,55 @@ fn test_duckdb_union_datatype() { stmt ); } + +#[test] +fn parse_use() { + std::assert_eq!( + duckdb().verified_stmt("USE mydb"), + Statement::Use { + db_name: Some(Ident::new("mydb")), + schema_name: None, + keyword: None + } + ); + std::assert_eq!( + duckdb().verified_stmt("USE mydb.my_schema"), + Statement::Use { + db_name: Some(Ident::new("mydb")), + schema_name: Some(Ident::new("my_schema")), + keyword: None + } + ); + assert_eq!( + duckdb().verified_stmt("USE DATABASE"), + Statement::Use { + db_name: Some(Ident::new("DATABASE")), + schema_name: None, + keyword: None + } + ); + assert_eq!( + duckdb().verified_stmt("USE SCHEMA"), + Statement::Use { + db_name: Some(Ident::new("SCHEMA")), + schema_name: None, + keyword: None + } + ); + assert_eq!( + duckdb().verified_stmt("USE CATALOG"), + Statement::Use { + db_name: Some(Ident::new("CATALOG")), + schema_name: None, + keyword: None + } + ); + assert_eq!( + duckdb().verified_stmt("USE CATALOG.SCHEMA"), + Statement::Use { + db_name: Some(Ident::new("CATALOG")), + schema_name: Some(Ident::new("SCHEMA")), + keyword: None + } + ); +} diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs index 157dad060..051e24ddb 100644 --- a/tests/sqlparser_hive.rs +++ b/tests/sqlparser_hive.rs @@ -401,6 +401,50 @@ fn parse_delimited_identifiers() { //TODO verified_stmt(r#"UPDATE foo SET "bar" = 5"#); } +#[test] +fn parse_use() { + assert_eq!( + hive().verified_stmt("USE mydb"), + Statement::Use { + db_name: Some(Ident::new("mydb")), + schema_name: None, + keyword: None + } + ); + assert_eq!( + hive().verified_stmt("USE DEFAULT"), + Statement::Use { + db_name: None, + schema_name: None, + keyword: Some("DEFAULT".to_string()) // Yes, as keyword not db_name + } + ); + assert_eq!( + hive().verified_stmt("USE DATABASE"), + Statement::Use { + db_name: Some(Ident::new("DATABASE")), + schema_name: None, + keyword: None + } + ); + assert_eq!( + hive().verified_stmt("USE SCHEMA"), + Statement::Use { + db_name: Some(Ident::new("SCHEMA")), + schema_name: None, + keyword: None + } + ); + assert_eq!( + hive().verified_stmt("USE CATALOG"), + Statement::Use { + db_name: Some(Ident::new("CATALOG")), + schema_name: None, + keyword: None + } + ); +} + fn hive() -> TestedDialects { TestedDialects { dialects: vec![Box::new(HiveDialect {})], diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 3e8b6afbf..1ba721c72 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -621,6 +621,42 @@ fn parse_mssql_declare() { ); } +#[test] +fn parse_use() { + assert_eq!( + ms().verified_stmt("USE mydb"), + Statement::Use { + db_name: Some(Ident::new("mydb")), + schema_name: None, + keyword: None + } + ); + assert_eq!( + ms().verified_stmt("USE DATABASE"), + Statement::Use { + db_name: Some(Ident::new("DATABASE")), + schema_name: None, + keyword: None + } + ); + assert_eq!( + ms().verified_stmt("USE SCHEMA"), + Statement::Use { + db_name: Some(Ident::new("SCHEMA")), + schema_name: None, + keyword: None + } + ); + assert_eq!( + ms().verified_stmt("USE CATALOG"), + Statement::Use { + db_name: Some(Ident::new("CATALOG")), + schema_name: None, + keyword: None + } + ); +} + fn ms() -> TestedDialects { TestedDialects { dialects: vec![Box::new(MsSqlDialect {})], diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 397a722b5..700c16a0a 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -447,7 +447,33 @@ fn parse_use() { assert_eq!( mysql_and_generic().verified_stmt("USE mydb"), Statement::Use { - db_name: Ident::new("mydb") + db_name: Some(Ident::new("mydb")), + schema_name: None, + keyword: None + } + ); + assert_eq!( + mysql_and_generic().verified_stmt("USE DATABASE"), + Statement::Use { + db_name: Some(Ident::new("DATABASE")), + schema_name: None, + keyword: None + } + ); + assert_eq!( + mysql_and_generic().verified_stmt("USE SCHEMA"), + Statement::Use { + db_name: Some(Ident::new("SCHEMA")), + schema_name: None, + keyword: None + } + ); + assert_eq!( + mysql_and_generic().verified_stmt("USE CATALOG"), + Statement::Use { + db_name: Some(Ident::new("CATALOG")), + schema_name: None, + keyword: None } ); } diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index a4f29c04f..eb3dd4748 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -2322,3 +2322,63 @@ fn parse_explain_table() { _ => panic!("Unexpected Statement, must be ExplainTable"), } } + +#[test] +fn parse_use() { + std::assert_eq!( + snowflake().verified_stmt("USE mydb"), + Statement::Use { + db_name: Some(Ident::new("mydb")), + schema_name: None, + keyword: None + } + ); + std::assert_eq!( + snowflake().verified_stmt("USE mydb.my_schema"), + Statement::Use { + db_name: Some(Ident::new("mydb")), + schema_name: Some(Ident::new("my_schema")), + keyword: None + } + ); + std::assert_eq!( + snowflake().verified_stmt("USE DATABASE mydb"), + Statement::Use { + db_name: Some(Ident::new("mydb")), + schema_name: None, + keyword: Some("DATABASE".to_string()), + } + ); + std::assert_eq!( + snowflake().verified_stmt("USE SCHEMA my_schema"), + Statement::Use { + db_name: None, + schema_name: Some(Ident::new("my_schema")), + keyword: Some("SCHEMA".to_string()) + } + ); + std::assert_eq!( + snowflake().verified_stmt("USE SCHEMA mydb.my_schema"), + Statement::Use { + db_name: Some(Ident::new("mydb")), + schema_name: Some(Ident::new("my_schema")), + keyword: Some("SCHEMA".to_string()) + } + ); + std::assert_eq!( + snowflake().verified_stmt("USE CATALOG"), + Statement::Use { + db_name: Some(Ident::new("CATALOG")), + schema_name: None, + keyword: None + } + ); + + let invalid_cases = ["USE SCHEMA", "USE DATABASE"]; + for sql in &invalid_cases { + std::assert_eq!( + snowflake().parse_sql_statements(sql).unwrap_err(), + ParserError::ParserError("Expected: identifier, found: EOF".to_string()), + ); + } +} From c3a71dabce0edb29bd2c4523e5a570133cacefb3 Mon Sep 17 00:00:00 2001 From: Kacper Muda Date: Thu, 22 Aug 2024 16:19:05 +0200 Subject: [PATCH 2/2] Change USE Statement to use Enum --- src/ast/dcl.rs | 27 +++++++++ src/ast/mod.rs | 33 ++--------- src/keywords.rs | 1 + src/parser/mod.rs | 68 ++++++--------------- tests/sqlparser_clickhouse.rs | 59 +++++++++---------- tests/sqlparser_databricks.rs | 82 ++++++++++++++++---------- tests/sqlparser_duckdb.rs | 90 ++++++++++++++-------------- tests/sqlparser_hive.rs | 60 ++++++++----------- tests/sqlparser_mssql.rs | 58 +++++++++--------- tests/sqlparser_mysql.rs | 59 +++++++++---------- tests/sqlparser_snowflake.rs | 108 ++++++++++++++++++++-------------- 11 files changed, 316 insertions(+), 329 deletions(-) diff --git a/src/ast/dcl.rs b/src/ast/dcl.rs index f90de34d4..1b0a77095 100644 --- a/src/ast/dcl.rs +++ b/src/ast/dcl.rs @@ -193,3 +193,30 @@ impl fmt::Display for AlterRoleOperation { } } } + +/// A `USE` (`Statement::Use`) operation +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum Use { + Catalog(ObjectName), // e.g. `USE CATALOG foo.bar` + Schema(ObjectName), // e.g. `USE SCHEMA foo.bar` + Database(ObjectName), // e.g. `USE DATABASE foo.bar` + Warehouse(ObjectName), // e.g. `USE WAREHOUSE foo.bar` + Object(ObjectName), // e.g. `USE foo.bar` + Default, // e.g. `USE DEFAULT` +} + +impl fmt::Display for Use { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("USE ")?; + match self { + Use::Catalog(name) => write!(f, "CATALOG {}", name), + Use::Schema(name) => write!(f, "SCHEMA {}", name), + Use::Database(name) => write!(f, "DATABASE {}", name), + Use::Warehouse(name) => write!(f, "WAREHOUSE {}", name), + Use::Object(name) => write!(f, "{}", name), + Use::Default => write!(f, "DEFAULT"), + } + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 89926a2b0..16b84ffeb 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -31,7 +31,7 @@ pub use self::data_type::{ ArrayElemTypeDef, CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, StructBracketKind, TimezoneInfo, }; -pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue}; +pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue, Use}; pub use self::ddl::{ AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef, ConstraintCharacteristics, Deduplicate, DeferrableInitial, GeneratedAs, @@ -2515,13 +2515,9 @@ pub enum Statement { /// Note: this is a MySQL-specific statement. ShowCollation { filter: Option }, /// ```sql - /// USE [DATABASE|SCHEMA|CATALOG|...] [.||] + /// `USE ...` /// ``` - Use { - db_name: Option, - schema_name: Option, - keyword: Option, - }, + Use(Use), /// ```sql /// START [ TRANSACTION | WORK ] | START TRANSACTION } ... /// ``` @@ -4127,28 +4123,7 @@ impl fmt::Display for Statement { } Ok(()) } - Statement::Use { - db_name, - schema_name, - keyword, - } => { - write!(f, "USE")?; - - if let Some(kw) = keyword.as_ref() { - write!(f, " {}", kw)?; - } - - if let Some(db_name) = db_name { - write!(f, " {}", db_name)?; - if let Some(schema_name) = schema_name { - write!(f, ".{}", schema_name)?; - } - } else if let Some(schema_name) = schema_name { - write!(f, " {}", schema_name)?; - } - - Ok(()) - } + Statement::Use(use_expr) => use_expr.fmt(f), Statement::ShowCollation { filter } => { write!(f, "SHOW COLLATION")?; if let Some(filter) = filter { diff --git a/src/keywords.rs b/src/keywords.rs index 4fe65ebca..a2addfc82 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -804,6 +804,7 @@ define_keywords!( VIEW, VIRTUAL, VOLATILE, + WAREHOUSE, WEEK, WHEN, WHENEVER, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 28e727ff5..a79a43d86 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -9225,63 +9225,31 @@ impl<'a> Parser<'a> { } pub fn parse_use(&mut self) -> Result { - // What should be treated as keyword in given dialect - let allowed_keywords = if dialect_of!(self is HiveDialect) { - vec![Keyword::DEFAULT] + // Determine which keywords are recognized by the current dialect + let parsed_keyword = if dialect_of!(self is HiveDialect) { + // HiveDialect accepts USE DEFAULT; statement without any db specified + if self.parse_keyword(Keyword::DEFAULT) { + return Ok(Statement::Use(Use::Default)); + } + None // HiveDialect doesn't expect any other specific keyword after `USE` } else if dialect_of!(self is DatabricksDialect) { - vec![Keyword::CATALOG, Keyword::DATABASE, Keyword::SCHEMA] + self.parse_one_of_keywords(&[Keyword::CATALOG, Keyword::DATABASE, Keyword::SCHEMA]) } else if dialect_of!(self is SnowflakeDialect) { - vec![Keyword::DATABASE, Keyword::SCHEMA] + self.parse_one_of_keywords(&[Keyword::DATABASE, Keyword::SCHEMA, Keyword::WAREHOUSE]) } else { - vec![] + None // No specific keywords for other dialects, including GenericDialect }; - let parsed_keyword = self.parse_one_of_keywords(&allowed_keywords); - // Hive dialect accepts USE DEFAULT; statement without any db specified - if dialect_of!(self is HiveDialect) && parsed_keyword == Some(Keyword::DEFAULT) { - return Ok(Statement::Use { - db_name: None, - schema_name: None, - keyword: Some("DEFAULT".to_string()), - }); - } - - // Parse the object name, which might be a single identifier or fully qualified name (e.g., x.y) - let parts = self.parse_object_name(false)?.0; - let (db_name, schema_name) = match parts.len() { - 1 => { - // Single identifier found - if dialect_of!(self is DatabricksDialect) { - if parsed_keyword == Some(Keyword::CATALOG) { - // Databricks: CATALOG keyword provided, treat as database name - (Some(parts[0].clone()), None) - } else { - // Databricks: DATABASE, SCHEMA or no keyword provided, treat as schema name - (None, Some(parts[0].clone())) - } - } else if dialect_of!(self is SnowflakeDialect) - && parsed_keyword == Some(Keyword::SCHEMA) - { - // Snowflake: SCHEMA keyword provided, treat as schema name - (None, Some(parts[0].clone())) - } else { - // Other dialects: treat as database name by default - (Some(parts[0].clone()), None) - } - } - 2 => (Some(parts[0].clone()), Some(parts[1].clone())), - _ => { - return Err(ParserError::ParserError( - "Invalid format in the USE statement".to_string(), - )) - } + let obj_name = self.parse_object_name(false)?; + let result = match parsed_keyword { + Some(Keyword::CATALOG) => Use::Catalog(obj_name), + Some(Keyword::DATABASE) => Use::Database(obj_name), + Some(Keyword::SCHEMA) => Use::Schema(obj_name), + Some(Keyword::WAREHOUSE) => Use::Warehouse(obj_name), + _ => Use::Object(obj_name), }; - Ok(Statement::Use { - db_name, - schema_name, - keyword: parsed_keyword.map(|kw| format!("{:?}", kw)), - }) + Ok(Statement::Use(result)) } pub fn parse_table_and_joins(&mut self) -> Result { diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index 52c9fa1d2..4b0694dc0 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -1162,38 +1162,35 @@ fn test_prewhere() { #[test] fn parse_use() { - assert_eq!( - clickhouse().verified_stmt("USE mydb"), - Statement::Use { - db_name: Some(Ident::new("mydb")), - schema_name: None, - keyword: None - } - ); - assert_eq!( - clickhouse().verified_stmt("USE DATABASE"), - Statement::Use { - db_name: Some(Ident::new("DATABASE")), - schema_name: None, - keyword: None - } - ); - assert_eq!( - clickhouse().verified_stmt("USE SCHEMA"), - Statement::Use { - db_name: Some(Ident::new("SCHEMA")), - schema_name: None, - keyword: None - } - ); - assert_eq!( - clickhouse().verified_stmt("USE CATALOG"), - Statement::Use { - db_name: Some(Ident::new("CATALOG")), - schema_name: None, - keyword: None + let valid_object_names = [ + "mydb", + "SCHEMA", + "DATABASE", + "CATALOG", + "WAREHOUSE", + "DEFAULT", + ]; + let quote_styles = ['"', '`']; + + for object_name in &valid_object_names { + // Test single identifier without quotes + assert_eq!( + clickhouse().verified_stmt(&format!("USE {}", object_name)), + Statement::Use(Use::Object(ObjectName(vec![Ident::new( + object_name.to_string() + )]))) + ); + for "e in "e_styles { + // Test single identifier with different type of quotes + assert_eq!( + clickhouse().verified_stmt(&format!("USE {0}{1}{0}", quote, object_name)), + Statement::Use(Use::Object(ObjectName(vec![Ident::with_quote( + quote, + object_name.to_string(), + )]))) + ); } - ); + } } #[test] diff --git a/tests/sqlparser_databricks.rs b/tests/sqlparser_databricks.rs index 9c3757090..ee0cf2d7d 100644 --- a/tests/sqlparser_databricks.rs +++ b/tests/sqlparser_databricks.rs @@ -192,47 +192,69 @@ fn test_values_clause() { #[test] fn parse_use() { - assert_eq!( - databricks().verified_stmt("USE my_schema"), - Statement::Use { - db_name: None, - schema_name: Some(Ident::new("my_schema")), - keyword: None + let valid_object_names = ["mydb", "WAREHOUSE", "DEFAULT"]; + let quote_styles = ['"', '`']; + + for object_name in &valid_object_names { + // Test single identifier without quotes + assert_eq!( + databricks().verified_stmt(&format!("USE {}", object_name)), + Statement::Use(Use::Object(ObjectName(vec![Ident::new( + object_name.to_string() + )]))) + ); + for "e in "e_styles { + // Test single identifier with different type of quotes + assert_eq!( + databricks().verified_stmt(&format!("USE {0}{1}{0}", quote, object_name)), + Statement::Use(Use::Object(ObjectName(vec![Ident::with_quote( + quote, + object_name.to_string(), + )]))) + ); } - ); + } + + for "e in "e_styles { + // Test single identifier with keyword and different type of quotes + assert_eq!( + databricks().verified_stmt(&format!("USE CATALOG {0}my_catalog{0}", quote)), + Statement::Use(Use::Catalog(ObjectName(vec![Ident::with_quote( + quote, + "my_catalog".to_string(), + )]))) + ); + assert_eq!( + databricks().verified_stmt(&format!("USE DATABASE {0}my_database{0}", quote)), + Statement::Use(Use::Database(ObjectName(vec![Ident::with_quote( + quote, + "my_database".to_string(), + )]))) + ); + assert_eq!( + databricks().verified_stmt(&format!("USE SCHEMA {0}my_schema{0}", quote)), + Statement::Use(Use::Schema(ObjectName(vec![Ident::with_quote( + quote, + "my_schema".to_string(), + )]))) + ); + } + + // Test single identifier with keyword and no quotes assert_eq!( databricks().verified_stmt("USE CATALOG my_catalog"), - Statement::Use { - db_name: Some(Ident::new("my_catalog")), - schema_name: None, - keyword: Some("CATALOG".to_string()) - } - ); - assert_eq!( - databricks().verified_stmt("USE CATALOG 'my_catalog'"), - Statement::Use { - db_name: Some(Ident::with_quote('\'', "my_catalog")), - schema_name: None, - keyword: Some("CATALOG".to_string()) - } + Statement::Use(Use::Catalog(ObjectName(vec![Ident::new("my_catalog")]))) ); assert_eq!( databricks().verified_stmt("USE DATABASE my_schema"), - Statement::Use { - db_name: None, - schema_name: Some(Ident::new("my_schema")), - keyword: Some("DATABASE".to_string()) - } + Statement::Use(Use::Database(ObjectName(vec![Ident::new("my_schema")]))) ); assert_eq!( databricks().verified_stmt("USE SCHEMA my_schema"), - Statement::Use { - db_name: None, - schema_name: Some(Ident::new("my_schema")), - keyword: Some("SCHEMA".to_string()) - } + Statement::Use(Use::Schema(ObjectName(vec![Ident::new("my_schema")]))) ); + // Test invalid syntax - missing identifier let invalid_cases = ["USE SCHEMA", "USE DATABASE", "USE CATALOG"]; for sql in &invalid_cases { assert_eq!( diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index 8702a663b..488fddfd3 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -759,52 +759,52 @@ fn test_duckdb_union_datatype() { #[test] fn parse_use() { - std::assert_eq!( - duckdb().verified_stmt("USE mydb"), - Statement::Use { - db_name: Some(Ident::new("mydb")), - schema_name: None, - keyword: None - } - ); - std::assert_eq!( - duckdb().verified_stmt("USE mydb.my_schema"), - Statement::Use { - db_name: Some(Ident::new("mydb")), - schema_name: Some(Ident::new("my_schema")), - keyword: None - } - ); - assert_eq!( - duckdb().verified_stmt("USE DATABASE"), - Statement::Use { - db_name: Some(Ident::new("DATABASE")), - schema_name: None, - keyword: None - } - ); - assert_eq!( - duckdb().verified_stmt("USE SCHEMA"), - Statement::Use { - db_name: Some(Ident::new("SCHEMA")), - schema_name: None, - keyword: None - } - ); - assert_eq!( - duckdb().verified_stmt("USE CATALOG"), - Statement::Use { - db_name: Some(Ident::new("CATALOG")), - schema_name: None, - keyword: None + let valid_object_names = [ + "mydb", + "SCHEMA", + "DATABASE", + "CATALOG", + "WAREHOUSE", + "DEFAULT", + ]; + let quote_styles = ['"', '\'']; + + for object_name in &valid_object_names { + // Test single identifier without quotes + assert_eq!( + duckdb().verified_stmt(&format!("USE {}", object_name)), + Statement::Use(Use::Object(ObjectName(vec![Ident::new( + object_name.to_string() + )]))) + ); + for "e in "e_styles { + // Test single identifier with different type of quotes + assert_eq!( + duckdb().verified_stmt(&format!("USE {0}{1}{0}", quote, object_name)), + Statement::Use(Use::Object(ObjectName(vec![Ident::with_quote( + quote, + object_name.to_string(), + )]))) + ); } - ); + } + + for "e in "e_styles { + // Test double identifier with different type of quotes + assert_eq!( + duckdb().verified_stmt(&format!("USE {0}CATALOG{0}.{0}my_schema{0}", quote)), + Statement::Use(Use::Object(ObjectName(vec![ + Ident::with_quote(quote, "CATALOG"), + Ident::with_quote(quote, "my_schema") + ]))) + ); + } + // Test double identifier without quotes assert_eq!( - duckdb().verified_stmt("USE CATALOG.SCHEMA"), - Statement::Use { - db_name: Some(Ident::new("CATALOG")), - schema_name: Some(Ident::new("SCHEMA")), - keyword: None - } + duckdb().verified_stmt("USE mydb.my_schema"), + Statement::Use(Use::Object(ObjectName(vec![ + Ident::new("mydb"), + Ident::new("my_schema") + ]))) ); } diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs index 051e24ddb..bd242035e 100644 --- a/tests/sqlparser_hive.rs +++ b/tests/sqlparser_hive.rs @@ -18,7 +18,7 @@ use sqlparser::ast::{ CreateFunctionBody, CreateFunctionUsing, Expr, Function, FunctionArgumentList, FunctionArguments, Ident, ObjectName, OneOrManyWithParens, SelectItem, Statement, TableFactor, - UnaryOperator, Value, + UnaryOperator, Use, Value, }; use sqlparser::dialect::{GenericDialect, HiveDialect, MsSqlDialect}; use sqlparser::parser::ParserError; @@ -403,45 +403,31 @@ fn parse_delimited_identifiers() { #[test] fn parse_use() { - assert_eq!( - hive().verified_stmt("USE mydb"), - Statement::Use { - db_name: Some(Ident::new("mydb")), - schema_name: None, - keyword: None + let valid_object_names = ["mydb", "SCHEMA", "DATABASE", "CATALOG", "WAREHOUSE"]; + let quote_styles = ['\'', '"', '`']; + for object_name in &valid_object_names { + // Test single identifier without quotes + assert_eq!( + hive().verified_stmt(&format!("USE {}", object_name)), + Statement::Use(Use::Object(ObjectName(vec![Ident::new( + object_name.to_string() + )]))) + ); + for "e in "e_styles { + // Test single identifier with different type of quotes + assert_eq!( + hive().verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)), + Statement::Use(Use::Object(ObjectName(vec![Ident::with_quote( + quote, + object_name.to_string(), + )]))) + ); } - ); + } + // Test DEFAULT keyword that is special case in Hive assert_eq!( hive().verified_stmt("USE DEFAULT"), - Statement::Use { - db_name: None, - schema_name: None, - keyword: Some("DEFAULT".to_string()) // Yes, as keyword not db_name - } - ); - assert_eq!( - hive().verified_stmt("USE DATABASE"), - Statement::Use { - db_name: Some(Ident::new("DATABASE")), - schema_name: None, - keyword: None - } - ); - assert_eq!( - hive().verified_stmt("USE SCHEMA"), - Statement::Use { - db_name: Some(Ident::new("SCHEMA")), - schema_name: None, - keyword: None - } - ); - assert_eq!( - hive().verified_stmt("USE CATALOG"), - Statement::Use { - db_name: Some(Ident::new("CATALOG")), - schema_name: None, - keyword: None - } + Statement::Use(Use::Default) ); } diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 1ba721c72..5c2ec8763 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -623,38 +623,34 @@ fn parse_mssql_declare() { #[test] fn parse_use() { - assert_eq!( - ms().verified_stmt("USE mydb"), - Statement::Use { - db_name: Some(Ident::new("mydb")), - schema_name: None, - keyword: None - } - ); - assert_eq!( - ms().verified_stmt("USE DATABASE"), - Statement::Use { - db_name: Some(Ident::new("DATABASE")), - schema_name: None, - keyword: None - } - ); - assert_eq!( - ms().verified_stmt("USE SCHEMA"), - Statement::Use { - db_name: Some(Ident::new("SCHEMA")), - schema_name: None, - keyword: None - } - ); - assert_eq!( - ms().verified_stmt("USE CATALOG"), - Statement::Use { - db_name: Some(Ident::new("CATALOG")), - schema_name: None, - keyword: None + let valid_object_names = [ + "mydb", + "SCHEMA", + "DATABASE", + "CATALOG", + "WAREHOUSE", + "DEFAULT", + ]; + let quote_styles = ['\'', '"']; + for object_name in &valid_object_names { + // Test single identifier without quotes + assert_eq!( + ms().verified_stmt(&format!("USE {}", object_name)), + Statement::Use(Use::Object(ObjectName(vec![Ident::new( + object_name.to_string() + )]))) + ); + for "e in "e_styles { + // Test single identifier with different type of quotes + assert_eq!( + ms().verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)), + Statement::Use(Use::Object(ObjectName(vec![Ident::with_quote( + quote, + object_name.to_string(), + )]))) + ); } - ); + } } fn ms() -> TestedDialects { diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 700c16a0a..33587c35a 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -444,38 +444,35 @@ fn parse_show_collation() { #[test] fn parse_use() { - assert_eq!( - mysql_and_generic().verified_stmt("USE mydb"), - Statement::Use { - db_name: Some(Ident::new("mydb")), - schema_name: None, - keyword: None - } - ); - assert_eq!( - mysql_and_generic().verified_stmt("USE DATABASE"), - Statement::Use { - db_name: Some(Ident::new("DATABASE")), - schema_name: None, - keyword: None - } - ); - assert_eq!( - mysql_and_generic().verified_stmt("USE SCHEMA"), - Statement::Use { - db_name: Some(Ident::new("SCHEMA")), - schema_name: None, - keyword: None - } - ); - assert_eq!( - mysql_and_generic().verified_stmt("USE CATALOG"), - Statement::Use { - db_name: Some(Ident::new("CATALOG")), - schema_name: None, - keyword: None + let valid_object_names = [ + "mydb", + "SCHEMA", + "DATABASE", + "CATALOG", + "WAREHOUSE", + "DEFAULT", + ]; + let quote_styles = ['\'', '"', '`']; + for object_name in &valid_object_names { + // Test single identifier without quotes + assert_eq!( + mysql_and_generic().verified_stmt(&format!("USE {}", object_name)), + Statement::Use(Use::Object(ObjectName(vec![Ident::new( + object_name.to_string() + )]))) + ); + for "e in "e_styles { + // Test single identifier with different type of quotes + assert_eq!( + mysql_and_generic() + .verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)), + Statement::Use(Use::Object(ObjectName(vec![Ident::with_quote( + quote, + object_name.to_string(), + )]))) + ); } - ); + } } #[test] diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index eb3dd4748..d0876fc50 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -2325,56 +2325,74 @@ fn parse_explain_table() { #[test] fn parse_use() { - std::assert_eq!( - snowflake().verified_stmt("USE mydb"), - Statement::Use { - db_name: Some(Ident::new("mydb")), - schema_name: None, - keyword: None + let valid_object_names = ["mydb", "CATALOG", "DEFAULT"]; + let quote_styles = ['\'', '"', '`']; + for object_name in &valid_object_names { + // Test single identifier without quotes + std::assert_eq!( + snowflake().verified_stmt(&format!("USE {}", object_name)), + Statement::Use(Use::Object(ObjectName(vec![Ident::new( + object_name.to_string() + )]))) + ); + for "e in "e_styles { + // Test single identifier with different type of quotes + std::assert_eq!( + snowflake().verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)), + Statement::Use(Use::Object(ObjectName(vec![Ident::with_quote( + quote, + object_name.to_string(), + )]))) + ); } - ); + } + + for "e in "e_styles { + // Test double identifier with different type of quotes + std::assert_eq!( + snowflake().verified_stmt(&format!("USE {0}CATALOG{0}.{0}my_schema{0}", quote)), + Statement::Use(Use::Object(ObjectName(vec![ + Ident::with_quote(quote, "CATALOG"), + Ident::with_quote(quote, "my_schema") + ]))) + ); + } + // Test double identifier without quotes std::assert_eq!( snowflake().verified_stmt("USE mydb.my_schema"), - Statement::Use { - db_name: Some(Ident::new("mydb")), - schema_name: Some(Ident::new("my_schema")), - keyword: None - } - ); - std::assert_eq!( - snowflake().verified_stmt("USE DATABASE mydb"), - Statement::Use { - db_name: Some(Ident::new("mydb")), - schema_name: None, - keyword: Some("DATABASE".to_string()), - } - ); - std::assert_eq!( - snowflake().verified_stmt("USE SCHEMA my_schema"), - Statement::Use { - db_name: None, - schema_name: Some(Ident::new("my_schema")), - keyword: Some("SCHEMA".to_string()) - } - ); - std::assert_eq!( - snowflake().verified_stmt("USE SCHEMA mydb.my_schema"), - Statement::Use { - db_name: Some(Ident::new("mydb")), - schema_name: Some(Ident::new("my_schema")), - keyword: Some("SCHEMA".to_string()) - } - ); - std::assert_eq!( - snowflake().verified_stmt("USE CATALOG"), - Statement::Use { - db_name: Some(Ident::new("CATALOG")), - schema_name: None, - keyword: None - } + Statement::Use(Use::Object(ObjectName(vec![ + Ident::new("mydb"), + Ident::new("my_schema") + ]))) ); - let invalid_cases = ["USE SCHEMA", "USE DATABASE"]; + for "e in "e_styles { + // Test single and double identifier with keyword and different type of quotes + std::assert_eq!( + snowflake().verified_stmt(&format!("USE DATABASE {0}my_database{0}", quote)), + Statement::Use(Use::Database(ObjectName(vec![Ident::with_quote( + quote, + "my_database".to_string(), + )]))) + ); + std::assert_eq!( + snowflake().verified_stmt(&format!("USE SCHEMA {0}my_schema{0}", quote)), + Statement::Use(Use::Schema(ObjectName(vec![Ident::with_quote( + quote, + "my_schema".to_string(), + )]))) + ); + std::assert_eq!( + snowflake().verified_stmt(&format!("USE SCHEMA {0}CATALOG{0}.{0}my_schema{0}", quote)), + Statement::Use(Use::Schema(ObjectName(vec![ + Ident::with_quote(quote, "CATALOG"), + Ident::with_quote(quote, "my_schema") + ]))) + ); + } + + // Test invalid syntax - missing identifier + let invalid_cases = ["USE SCHEMA", "USE DATABASE", "USE WAREHOUSE"]; for sql in &invalid_cases { std::assert_eq!( snowflake().parse_sql_statements(sql).unwrap_err(),