Added alter external table support for snowflake (#2122)

This commit is contained in:
Andriy Romanov 2025-12-10 03:51:44 -08:00 committed by GitHub
parent adbfc46177
commit 048bc8f09d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 74 additions and 8 deletions

View file

@ -371,10 +371,15 @@ pub enum AlterTableOperation {
DropClusteringKey,
SuspendRecluster,
ResumeRecluster,
/// `REFRESH`
/// `REFRESH [ '<subpath>' ]`
///
/// Note: this is Snowflake specific for dynamic tables <https://docs.snowflake.com/en/sql-reference/sql/alter-table>
Refresh,
/// Note: this is Snowflake specific for dynamic/external tables
/// <https://docs.snowflake.com/en/sql-reference/sql/alter-dynamic-table>
/// <https://docs.snowflake.com/en/sql-reference/sql/alter-external-table>
Refresh {
/// Optional subpath for external table refresh
subpath: Option<String>,
},
/// `SUSPEND`
///
/// Note: this is Snowflake specific for dynamic tables <https://docs.snowflake.com/en/sql-reference/sql/alter-table>
@ -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")
@ -3977,8 +3986,11 @@ pub enum AlterTableType {
/// <https://docs.snowflake.com/en/sql-reference/sql/alter-iceberg-table>
Iceberg,
/// Dynamic table type
/// <https://docs.snowflake.com/en/sql-reference/sql/alter-table>
/// <https://docs.snowflake.com/en/sql-reference/sql/alter-dynamic-table>
Dynamic,
/// External table type
/// <https://docs.snowflake.com/en/sql-reference/sql/alter-external-table>
External,
}
/// ALTER TABLE statement
@ -4008,6 +4020,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 ")?,
}

View file

@ -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(),

View file

@ -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<Statement, ParserErr
// Parse the operation (REFRESH, SUSPEND, or RESUME)
let operation = if parser.parse_keyword(Keyword::REFRESH) {
AlterTableOperation::Refresh
AlterTableOperation::Refresh { subpath: None }
} else if parser.parse_keyword(Keyword::SUSPEND) {
AlterTableOperation::Suspend
} else if parser.parse_keyword(Keyword::RESUME) {
@ -649,6 +654,45 @@ fn parse_alter_dynamic_table(parser: &mut Parser) -> Result<Statement, ParserErr
}))
}
/// Parse snowflake alter external table.
/// <https://docs.snowflake.com/en/sql-reference/sql/alter-external-table>
fn parse_alter_external_table(parser: &mut Parser) -> Result<Statement, ParserError> {
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.
/// <https://docs.snowflake.com/en/sql-reference/sql/alter-session>
fn parse_alter_session(parser: &mut Parser, set: bool) -> Result<Statement, ParserError> {

View file

@ -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/'");
}