diff --git a/src/ast/dcl.rs b/src/ast/dcl.rs index fcc15c7da..6eb528ca9 100644 --- a/src/ast/dcl.rs +++ b/src/ast/dcl.rs @@ -31,7 +31,7 @@ use sqlparser_derive::{Visit, VisitMut}; use super::{display_comma_separated, Expr, Ident, Password, Spanned}; use crate::ast::{ display_separated, CascadeOption, CurrentGrantsKind, GrantObjects, Grantee, ObjectName, - Privileges, + Privileges, Tag, }; use crate::tokenizer::Span; @@ -349,6 +349,9 @@ pub struct CreateRole { // MSSQL /// Optional authorization owner. pub authorization_owner: Option, + // Snowflake + /// Trailing `WITH TAG ( = '' [, ...])` clause; empty when absent. + pub with_tags: Vec, } impl fmt::Display for CreateRole { @@ -424,6 +427,9 @@ impl fmt::Display for CreateRole { if let Some(owner) = &self.authorization_owner { write!(f, " AUTHORIZATION {owner}")?; } + if !self.with_tags.is_empty() { + write!(f, " WITH TAG ({})", display_comma_separated(&self.with_tags))?; + } Ok(()) } } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 5fdfab535..e8493c890 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -5405,6 +5405,8 @@ pub enum Statement { object_type: ObjectType, /// The target object name. object_name: ObjectName, + /// Whether `IF EXISTS` was specified, suppressing the missing-target error. + if_exists: bool, /// Whether this is `UNSET TAG` (`true`) or `SET TAG` (`false`). unset: bool, /// Tags to set (`SET TAG`); empty for `UNSET TAG`. @@ -8023,11 +8025,16 @@ impl fmt::Display for Statement { Statement::SetTags { object_type, object_name, + if_exists, unset, set_tags, unset_tags, } => { - write!(f, "ALTER {object_type} {object_name} ")?; + write!(f, "ALTER {object_type} ")?; + if *if_exists { + write!(f, "IF EXISTS ")?; + } + write!(f, "{object_name} ")?; if *unset { write!(f, "UNSET TAG {}", display_comma_separated(unset_tags)) } else { diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 0090f0bda..ae1712d05 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -377,6 +377,17 @@ impl Dialect for SnowflakeDialect { return Some(Ok(stmt)); } + // ALTER ROLE [IF EXISTS] { SET TAG | UNSET TAG } — intercept only + // the tag form (mirroring ALTER SCHEMA); every other ALTER ROLE form + // (RENAME TO, SET/UNSET COMMENT) fails the closure and falls through to + // the generic grammar. + if let Ok(Some(stmt)) = parser.maybe_parse(|p| { + p.expect_keywords(&[Keyword::ALTER, Keyword::ROLE])?; + parse_alter_object_set_tags(p, ObjectType::Role) + }) { + return Some(Ok(stmt)); + } + if parser.parse_keywords(&[Keyword::ALTER, Keyword::STAGE]) { // ALTER STAGE return Some(parse_alter_stage(parser)); @@ -3331,6 +3342,7 @@ fn parse_alter_object_set_tags( parser: &mut Parser, object_type: ObjectType, ) -> Result { + let if_exists = parser.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); let object_name = parser.parse_object_name(false)?; let unset = match parser.expect_one_of_keywords(&[Keyword::SET, Keyword::UNSET])? { Keyword::UNSET => true, @@ -3362,6 +3374,7 @@ fn parse_alter_object_set_tags( Ok(Statement::SetTags { object_type, object_name, + if_exists, unset, set_tags, unset_tags, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ef771a2de..13077ff56 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -7316,6 +7316,19 @@ impl<'a> Parser<'a> { let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); let names = self.parse_comma_separated(|p| p.parse_object_name(false))?; + // Snowflake: trailing `WITH TAG ( = '' [, ...] )`. It is + // trailing-only, so no other role option may follow. `WITH TAG` is + // consumed atomically here; a bare `WITH` (or `WITH `) is left + // for the generic option loop below. + let mut with_tags = Vec::new(); + if dialect_of!(self is SnowflakeDialect) + && self.parse_keywords(&[Keyword::WITH, Keyword::TAG]) + { + self.expect_token(&Token::LParen)?; + with_tags = self.parse_comma_separated(Parser::parse_tag)?; + self.expect_token(&Token::RParen)?; + } + let _ = self.parse_keyword(Keyword::WITH); // [ WITH ] let optional_keywords = if dialect_of!(self is MsSqlDialect) { @@ -7534,6 +7547,7 @@ impl<'a> Parser<'a> { user, admin, authorization_owner, + with_tags, }) }