diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index ad3191ebc..40c1d2bdf 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -371,10 +371,15 @@ pub enum AlterTableOperation { DropClusteringKey, SuspendRecluster, ResumeRecluster, - /// `REFRESH` + /// `REFRESH [ '' ]` /// - /// Note: this is Snowflake specific for dynamic tables - Refresh, + /// Note: this is Snowflake specific for dynamic/external tables + /// + /// + Refresh { + /// Optional subpath for external table refresh + subpath: Option, + }, /// `SUSPEND` /// /// Note: this is Snowflake specific for dynamic tables @@ -863,8 +868,12 @@ impl fmt::Display for AlterTableOperation { write!(f, "RESUME RECLUSTER")?; Ok(()) } - AlterTableOperation::Refresh => { - write!(f, "REFRESH") + AlterTableOperation::Refresh { subpath } => { + write!(f, "REFRESH")?; + if let Some(path) = subpath { + write!(f, " '{path}'")?; + } + Ok(()) } AlterTableOperation::Suspend => { write!(f, "SUSPEND") @@ -3880,8 +3889,11 @@ pub enum AlterTableType { /// Iceberg, /// Dynamic table type - /// + /// Dynamic, + /// External table type + /// + External, } /// ALTER TABLE statement @@ -3911,6 +3923,7 @@ impl fmt::Display for AlterTable { match &self.table_type { Some(AlterTableType::Iceberg) => write!(f, "ALTER ICEBERG TABLE ")?, Some(AlterTableType::Dynamic) => write!(f, "ALTER DYNAMIC TABLE ")?, + Some(AlterTableType::External) => write!(f, "ALTER EXTERNAL TABLE ")?, None => write!(f, "ALTER TABLE ")?, } diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 684cc5b02..15f1d580f 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -1145,7 +1145,7 @@ impl Spanned for AlterTableOperation { AlterTableOperation::DropClusteringKey => Span::empty(), AlterTableOperation::SuspendRecluster => Span::empty(), AlterTableOperation::ResumeRecluster => Span::empty(), - AlterTableOperation::Refresh => Span::empty(), + AlterTableOperation::Refresh { .. } => Span::empty(), AlterTableOperation::Suspend => Span::empty(), AlterTableOperation::Resume => Span::empty(), AlterTableOperation::Algorithm { .. } => Span::empty(), diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 4cfaddceb..ed01c128b 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -221,6 +221,11 @@ impl Dialect for SnowflakeDialect { return Some(parse_alter_dynamic_table(parser)); } + if parser.parse_keywords(&[Keyword::ALTER, Keyword::EXTERNAL, Keyword::TABLE]) { + // ALTER EXTERNAL TABLE + return Some(parse_alter_external_table(parser)); + } + if parser.parse_keywords(&[Keyword::ALTER, Keyword::SESSION]) { // ALTER SESSION let set = match parser.parse_one_of_keywords(&[Keyword::SET, Keyword::UNSET]) { @@ -619,7 +624,7 @@ fn parse_alter_dynamic_table(parser: &mut Parser) -> Result Result +fn parse_alter_external_table(parser: &mut Parser) -> Result { + let if_exists = parser.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); + let table_name = parser.parse_object_name(true)?; + + // Parse the operation (REFRESH for now) + let operation = if parser.parse_keyword(Keyword::REFRESH) { + // Optional subpath for refreshing specific partitions + let subpath = match parser.peek_token().token { + Token::SingleQuotedString(s) => { + parser.next_token(); + Some(s) + } + _ => None, + }; + AlterTableOperation::Refresh { subpath } + } else { + return parser.expected("REFRESH after ALTER EXTERNAL TABLE", parser.peek_token()); + }; + + let end_token = if parser.peek_token_ref().token == Token::SemiColon { + parser.peek_token_ref().clone() + } else { + parser.get_current_token().clone() + }; + + Ok(Statement::AlterTable(AlterTable { + name: table_name, + if_exists, + only: false, + operations: vec![operation], + location: None, + on_cluster: None, + table_type: Some(AlterTableType::External), + end_token: AttachedToken(end_token), + })) +} + /// Parse snowflake alter session. /// fn parse_alter_session(parser: &mut Parser, set: bool) -> Result { diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 22a632666..37e9f8cb4 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -4635,3 +4635,12 @@ fn test_alter_dynamic_table() { snowflake().verified_stmt("ALTER DYNAMIC TABLE my_dyn_table SUSPEND"); snowflake().verified_stmt("ALTER DYNAMIC TABLE my_dyn_table RESUME"); } + +#[test] +fn test_alter_external_table() { + snowflake().verified_stmt("ALTER EXTERNAL TABLE some_table REFRESH"); + snowflake().verified_stmt("ALTER EXTERNAL TABLE some_table REFRESH 'year=2025/month=12/'"); + snowflake().verified_stmt("ALTER EXTERNAL TABLE IF EXISTS some_table REFRESH"); + snowflake() + .verified_stmt("ALTER EXTERNAL TABLE IF EXISTS some_table REFRESH 'year=2025/month=12/'"); +}