From d2466af20a5336c1554bdb82b0c84d04a20db26e Mon Sep 17 00:00:00 2001 From: Yoav Cohen <59807311+yoavcloud@users.noreply.github.com> Date: Sat, 5 Jul 2025 08:18:58 +0200 Subject: [PATCH] Add support for dropping multiple columns in Snowflake (#1918) --- src/ast/ddl.rs | 8 ++++---- src/ast/spans.rs | 4 ++-- src/dialect/mod.rs | 5 +++++ src/dialect/snowflake.rs | 4 ++++ src/parser/mod.rs | 8 ++++++-- tests/sqlparser_common.rs | 7 +++++-- tests/sqlparser_mysql.rs | 4 ++-- 7 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 7e46a59f..51e05784 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -140,10 +140,10 @@ pub enum AlterTableOperation { name: Ident, drop_behavior: Option, }, - /// `DROP [ COLUMN ] [ IF EXISTS ] [ CASCADE ]` + /// `DROP [ COLUMN ] [ IF EXISTS ] [ , , ... ] [ CASCADE ]` DropColumn { has_column_keyword: bool, - column_name: Ident, + column_names: Vec, if_exists: bool, drop_behavior: Option, }, @@ -631,7 +631,7 @@ impl fmt::Display for AlterTableOperation { AlterTableOperation::DropIndex { name } => write!(f, "DROP INDEX {name}"), AlterTableOperation::DropColumn { has_column_keyword, - column_name, + column_names: column_name, if_exists, drop_behavior, } => write!( @@ -639,7 +639,7 @@ impl fmt::Display for AlterTableOperation { "DROP {}{}{}{}", if *has_column_keyword { "COLUMN " } else { "" }, if *if_exists { "IF EXISTS " } else { "" }, - column_name, + display_comma_separated(column_name), match drop_behavior { None => "", Some(DropBehavior::Restrict) => " RESTRICT", diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 1d790ab6..144de592 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -1112,10 +1112,10 @@ impl Spanned for AlterTableOperation { } => name.span, AlterTableOperation::DropColumn { has_column_keyword: _, - column_name, + column_names, if_exists: _, drop_behavior: _, - } => column_name.span, + } => union_spans(column_names.iter().map(|i| i.span)), AlterTableOperation::AttachPartition { partition } => partition.span(), AlterTableOperation::DetachPartition { partition } => partition.span(), AlterTableOperation::FreezePartition { diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 3345380c..bc3c5555 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1071,6 +1071,11 @@ pub trait Dialect: Debug + Any { fn supports_alter_column_type_using(&self) -> bool { false } + + /// Returns true if the dialect supports `ALTER TABLE tbl DROP COLUMN c1, ..., cn` + fn supports_comma_separated_drop_column_list(&self) -> bool { + false + } } /// This represents the operators for which precedence must be defined diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index ee770b0f..f56db9b8 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -364,6 +364,10 @@ impl Dialect for SnowflakeDialect { fn supports_space_separated_column_options(&self) -> bool { true } + + fn supports_comma_separated_drop_column_list(&self) -> bool { + true + } } fn parse_file_staging_command(kw: Keyword, parser: &mut Parser) -> Result { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 32f8a97e..839d3645 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8675,11 +8675,15 @@ impl<'a> Parser<'a> { } else { let has_column_keyword = self.parse_keyword(Keyword::COLUMN); // [ COLUMN ] let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); - let column_name = self.parse_identifier()?; + let column_names = if self.dialect.supports_comma_separated_drop_column_list() { + self.parse_comma_separated(Parser::parse_identifier)? + } else { + vec![self.parse_identifier()?] + }; let drop_behavior = self.parse_optional_drop_behavior(); AlterTableOperation::DropColumn { has_column_keyword, - column_name, + column_names, if_exists, drop_behavior, } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 1bdd302e..ed9bb704 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -4996,15 +4996,18 @@ fn parse_alter_table_drop_column() { "ALTER TABLE tab DROP is_active CASCADE", ); + let dialects = all_dialects_where(|d| d.supports_comma_separated_drop_column_list()); + dialects.verified_stmt("ALTER TABLE tbl DROP COLUMN c1, c2, c3"); + fn check_one(constraint_text: &str) { match alter_table_op(verified_stmt(&format!("ALTER TABLE tab {constraint_text}"))) { AlterTableOperation::DropColumn { has_column_keyword: true, - column_name, + column_names, if_exists, drop_behavior, } => { - assert_eq!("is_active", column_name.to_string()); + assert_eq!("is_active", column_names.first().unwrap().to_string()); assert!(if_exists); match drop_behavior { None => assert!(constraint_text.ends_with(" is_active")), diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 9224a003..79e2259b 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -2876,7 +2876,7 @@ fn parse_alter_table_with_algorithm() { vec![ AlterTableOperation::DropColumn { has_column_keyword: true, - column_name: Ident::new("password_digest"), + column_names: vec![Ident::new("password_digest")], if_exists: false, drop_behavior: None, }, @@ -2924,7 +2924,7 @@ fn parse_alter_table_with_lock() { vec![ AlterTableOperation::DropColumn { has_column_keyword: true, - column_name: Ident::new("password_digest"), + column_names: vec![Ident::new("password_digest")], if_exists: false, drop_behavior: None, },