From 41f3c06c662d30570c75c6be7fbcc597308b2bbf Mon Sep 17 00:00:00 2001 From: Przemyslaw Denkiewicz Date: Wed, 12 Aug 2026 17:04:05 +0200 Subject: [PATCH] =?UTF-8?q?Snowflake:=20stage=20tagging=20grammar=20?= =?UTF-8?q?=E2=80=94=20ALTER=20STAGE=20[IF=20EXISTS]=20SET/UNSET=20TAG,=20?= =?UTF-8?q?CREATE=20STAGE=20...=20WITH=20TAG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Route ALTER STAGE { SET | UNSET } TAG through parse_alter_object_set_tags (mirroring the ALTER WAREHOUSE maybe_parse form) so the tag form requires a name and non-tag forms keep falling through to parse_alter_stage. Add a trailing-only WITH TAG (...) clause to CREATE STAGE via a new with_tags field. Co-authored-by: Claude Opus 4.8 --- src/ast/mod.rs | 6 ++++++ src/dialect/snowflake.rs | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a7396dc09..335eae5e8 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -4956,6 +4956,8 @@ pub enum Statement { copy_options: KeyValueOptions, /// Optional comment. comment: Option, + /// Trailing `WITH TAG ( = '' [, ...])` clause; empty when absent. + with_tags: Vec, }, /// ```sql /// ALTER STAGE [IF EXISTS] { SET ... | RENAME TO } @@ -7563,6 +7565,7 @@ impl fmt::Display for Statement { file_format, copy_options, comment, + with_tags, .. } => { write!( @@ -7584,6 +7587,9 @@ impl fmt::Display for Statement { if comment.is_some() { write!(f, " COMMENT='{}'", comment.as_ref().unwrap())?; } + if !with_tags.is_empty() { + write!(f, " WITH TAG ({})", display_comma_separated(with_tags))?; + } Ok(()) } Statement::AlterStage { diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index e87bf906f..dcf88382c 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -389,6 +389,17 @@ impl Dialect for SnowflakeDialect { return Some(Ok(stmt)); } + // ALTER STAGE [IF EXISTS] { SET TAG | UNSET TAG } — intercept only + // the tag form. Every other ALTER STAGE form (SET , RENAME TO) + // fails the closure (the `TAG` keyword is absent) and falls through to + // parse_alter_stage, which keeps the bare (name-less) form a syntax error. + if let Ok(Some(stmt)) = parser.maybe_parse(|p| { + p.expect_keywords(&[Keyword::ALTER, Keyword::STAGE])?; + parse_alter_object_set_tags(p, ObjectType::Stage) + }) { + return Some(Ok(stmt)); + } + if parser.parse_keywords(&[Keyword::ALTER, Keyword::STAGE]) { // ALTER STAGE return Some(parse_alter_stage(parser)); @@ -1963,6 +1974,16 @@ pub fn parse_create_stage( comment, } = parse_stage_properties(parser)?; + // Trailing `WITH TAG ( = '' [, ...])`. The property loop above breaks + // on the `WITH` keyword, so the clause is naturally trailing-only. + let mut with_tags = Vec::new(); + if parser.parse_keyword(Keyword::WITH) { + parser.expect_keyword(Keyword::TAG)?; + parser.expect_token(&Token::LParen)?; + with_tags = parser.parse_comma_separated(Parser::parse_tag)?; + parser.expect_token(&Token::RParen)?; + } + Ok(Statement::CreateStage { or_replace, temporary, @@ -1973,6 +1994,7 @@ pub fn parse_create_stage( file_format, copy_options, comment, + with_tags, }) }