diff --git a/fuzz/fuzz_seeds/mssql_2702_alter_role_rename.sql b/fuzz/fuzz_seeds/mssql_2702_alter_role_rename.sql new file mode 100644 index 0000000000..d563c54909 --- /dev/null +++ b/fuzz/fuzz_seeds/mssql_2702_alter_role_rename.sql @@ -0,0 +1 @@ +ALTER ROLE old_name WITH NAME = new_name diff --git a/fuzz/fuzz_seeds/mssql_2702_declare_list.sql b/fuzz/fuzz_seeds/mssql_2702_declare_list.sql new file mode 100644 index 0000000000..844dae76bb --- /dev/null +++ b/fuzz/fuzz_seeds/mssql_2702_declare_list.sql @@ -0,0 +1 @@ +DECLARE @foo CURSOR, @bar INT, @baz AS TEXT = 'foobar' diff --git a/src/ast/dcl.rs b/src/ast/dcl.rs index 96b340714c..285d12d834 100644 --- a/src/ast/dcl.rs +++ b/src/ast/dcl.rs @@ -150,6 +150,14 @@ pub enum AlterRoleOperation { }, /// MS SQL Server /// + /// + /// `ALTER ROLE role_name WITH NAME = new_name` + RenameRoleWithName { + /// New name for the role. + role_name: Ident, + }, + /// MS SQL Server + /// AddMember { /// Member name to add to the role. member_name: Ident, @@ -197,6 +205,9 @@ impl fmt::Display for AlterRoleOperation { AlterRoleOperation::RenameRole { role_name } => { write!(f, "RENAME TO {role_name}") } + AlterRoleOperation::RenameRoleWithName { role_name } => { + write!(f, "WITH NAME = {role_name}") + } AlterRoleOperation::AddMember { member_name } => { write!(f, "ADD MEMBER {member_name}") } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index f38ff0f17a..dcb94b42f9 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3097,6 +3097,19 @@ impl fmt::Display for DeclareAssignment { } } +/// The delimiter separating the declarations collected by a `DECLARE` +/// statement ([Statement::Declare]). +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum DeclareDelimiter { + /// Comma-separated declarations, e.g. `DECLARE @a INT, @b INT` (MsSql). + Comma, + /// Semicolon-separated declarations, e.g. `DECLARE a INT; b INT` + /// (Snowflake). + Semicolon, +} + /// Represents the type of a `DECLARE` statement. #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -4088,6 +4101,8 @@ pub enum Statement { Declare { /// Cursor declaration statements collected by `DECLARE`. stmts: Vec, + /// The delimiter used between the collected declarations. + delimiter: DeclareDelimiter, }, /// ```sql /// CREATE EXTENSION [ IF NOT EXISTS ] extension_name @@ -5266,9 +5281,16 @@ impl fmt::Display for Statement { write!(f, "{statement}") } Statement::Query(s) => s.fmt(f), - Statement::Declare { stmts } => { + Statement::Declare { stmts, delimiter } => { write!(f, "DECLARE ")?; - write!(f, "{}", display_separated(stmts, "; ")) + match delimiter { + DeclareDelimiter::Comma => { + write!(f, "{}", display_comma_separated(stmts)) + } + DeclareDelimiter::Semicolon => { + write!(f, "{}", display_separated(stmts, "; ")) + } + } } Statement::Fetch { name, diff --git a/src/parser/alter.rs b/src/parser/alter.rs index 4000eb26ba..8ad33110ef 100644 --- a/src/parser/alter.rs +++ b/src/parser/alter.rs @@ -356,7 +356,7 @@ impl Parser<'_> { } else if self.parse_keywords(&[Keyword::WITH, Keyword::NAME]) { if self.consume_token(&Token::Eq) { let role_name = self.parse_identifier()?; - AlterRoleOperation::RenameRole { role_name } + AlterRoleOperation::RenameRoleWithName { role_name } } else { return self.expected_ref("= after WITH NAME ", self.peek_token_ref()); } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 95ff188faa..4591636543 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8004,6 +8004,7 @@ impl<'a> Parser<'a> { let query = Some(self.parse_query()?); Ok(Statement::Declare { + delimiter: DeclareDelimiter::Comma, stmts: vec![Declare { names: vec![name], data_type: None, @@ -8047,6 +8048,7 @@ impl<'a> Parser<'a> { }; Ok(Statement::Declare { + delimiter: DeclareDelimiter::Comma, stmts: vec![Declare { names, data_type, @@ -8174,7 +8176,10 @@ impl<'a> Parser<'a> { break; } - Ok(Statement::Declare { stmts }) + Ok(Statement::Declare { + stmts, + delimiter: DeclareDelimiter::Semicolon, + }) } /// Parse a [MsSql] `DECLARE` statement. @@ -8191,7 +8196,10 @@ impl<'a> Parser<'a> { pub fn parse_mssql_declare(&mut self) -> Result { let stmts = self.parse_comma_separated(Parser::parse_mssql_declare_stmt)?; - Ok(Statement::Declare { stmts }) + Ok(Statement::Declare { + stmts, + delimiter: DeclareDelimiter::Comma, + }) } /// Parse the body of a [MsSql] `DECLARE`statement. diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index daebe63dd7..612d3c2b00 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -2191,7 +2191,7 @@ fn parse_big_query_declare() { ), ] { match bigquery().verified_stmt(sql) { - Statement::Declare { mut stmts } => { + Statement::Declare { mut stmts, .. } => { assert_eq!(1, stmts.len()); let Declare { names, diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index e07d87007d..0d6cca982c 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -29,6 +29,7 @@ use test_utils::*; use sqlparser::ast::DataType::{Int, Text, Varbinary}; use sqlparser::ast::DeclareAssignment::MsSqlAssignment; +use sqlparser::ast::DeclareDelimiter; use sqlparser::ast::Value::SingleQuotedString; use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, MsSqlDialect}; @@ -800,21 +801,21 @@ fn parse_mssql_create_role() { fn parse_alter_role() { let sql = "ALTER ROLE old_name WITH NAME = new_name"; assert_eq!( - ms().parse_sql_statements(sql).unwrap(), - [Statement::AlterRole { + ms().verified_stmt(sql), + Statement::AlterRole { name: Ident { value: "old_name".into(), quote_style: None, span: Span::empty(), }, - operation: AlterRoleOperation::RenameRole { + operation: AlterRoleOperation::RenameRoleWithName { role_name: Ident { value: "new_name".into(), quote_style: None, span: Span::empty(), } }, - }] + } ); let sql = "ALTER ROLE role_name ADD MEMBER new_member"; @@ -1423,10 +1424,12 @@ fn parse_substring_in_select() { #[test] fn parse_mssql_declare() { let sql = "DECLARE @foo CURSOR, @bar INT, @baz AS TEXT = 'foobar';"; + let _ = ms().verified_stmt("DECLARE @foo CURSOR, @bar INT, @baz TEXT = 'foobar'"); let ast = ms().parse_sql_statements(sql).unwrap(); assert_eq!( vec![Statement::Declare { + delimiter: DeclareDelimiter::Comma, stmts: vec![ Declare { names: vec![Ident { @@ -1485,6 +1488,7 @@ fn parse_mssql_declare() { assert_eq!( vec![ Statement::Declare { + delimiter: DeclareDelimiter::Comma, stmts: vec![Declare { names: vec![Ident::new("@bar"),], data_type: Some(Int(None)), diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 6c8c73c6dd..0c37194467 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -1739,7 +1739,7 @@ fn parse_snowflake_declare_cursor() { ), ] { match snowflake().verified_stmt(sql) { - Statement::Declare { mut stmts } => { + Statement::Declare { mut stmts, .. } => { assert_eq!(1, stmts.len()); let Declare { names, @@ -1807,7 +1807,7 @@ fn parse_snowflake_declare_result_set() { ("DECLARE res RESULTSET", "res", None), ] { match snowflake().verified_stmt(sql) { - Statement::Declare { mut stmts } => { + Statement::Declare { mut stmts, .. } => { assert_eq!(1, stmts.len()); let Declare { names, @@ -1860,7 +1860,7 @@ fn parse_snowflake_declare_exception() { ("DECLARE ex EXCEPTION", "ex", None), ] { match snowflake().verified_stmt(sql) { - Statement::Declare { mut stmts } => { + Statement::Declare { mut stmts, .. } => { assert_eq!(1, stmts.len()); let Declare { names, @@ -1900,7 +1900,7 @@ fn parse_snowflake_declare_variable() { ("DECLARE profit", "profit", None, None), ] { match snowflake().verified_stmt(sql) { - Statement::Declare { mut stmts } => { + Statement::Declare { mut stmts, .. } => { assert_eq!(1, stmts.len()); let Declare { names, @@ -1950,7 +1950,7 @@ fn parse_snowflake_declare_multi_statements() { "ex EXCEPTION (-20003, 'ERROR: Could not create table.')" ); match snowflake().verified_stmt(sql) { - Statement::Declare { stmts } => { + Statement::Declare { stmts, .. } => { let actual = stmts .iter() .map(|stmt| (stmt.names[0].value.as_str(), stmt.declare_type.clone()))