Add snowflake dynamic table parsing (#2083)
diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs
index fd48121..1ae24a7 100644
--- a/src/ast/ddl.rs
+++ b/src/ast/ddl.rs
@@ -365,6 +365,18 @@
DropClusteringKey,
SuspendRecluster,
ResumeRecluster,
+ /// `REFRESH`
+ ///
+ /// Note: this is Snowflake specific for dynamic tables <https://docs.snowflake.com/en/sql-reference/sql/alter-table>
+ Refresh,
+ /// `SUSPEND`
+ ///
+ /// Note: this is Snowflake specific for dynamic tables <https://docs.snowflake.com/en/sql-reference/sql/alter-table>
+ Suspend,
+ /// `RESUME`
+ ///
+ /// Note: this is Snowflake specific for dynamic tables <https://docs.snowflake.com/en/sql-reference/sql/alter-table>
+ Resume,
/// `ALGORITHM [=] { DEFAULT | INSTANT | INPLACE | COPY }`
///
/// [MySQL]-specific table alter algorithm.
@@ -845,6 +857,15 @@
write!(f, "RESUME RECLUSTER")?;
Ok(())
}
+ AlterTableOperation::Refresh => {
+ write!(f, "REFRESH")
+ }
+ AlterTableOperation::Suspend => {
+ write!(f, "SUSPEND")
+ }
+ AlterTableOperation::Resume => {
+ write!(f, "RESUME")
+ }
AlterTableOperation::AutoIncrement { equals, value } => {
write!(
f,
@@ -3532,6 +3553,20 @@
}
}
+/// Table type for ALTER TABLE statements.
+/// Used to distinguish between regular tables, Iceberg tables, and Dynamic tables.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum AlterTableType {
+ /// Iceberg table type
+ /// <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>
+ Dynamic,
+}
+
/// ALTER TABLE statement
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -3548,19 +3583,18 @@
/// For example: `ALTER TABLE table_name ON CLUSTER cluster_name ADD COLUMN c UInt32`
/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/update)
pub on_cluster: Option<Ident>,
- /// Snowflake "ICEBERG" clause for Iceberg tables
- /// <https://docs.snowflake.com/en/sql-reference/sql/alter-iceberg-table>
- pub iceberg: bool,
+ /// Table type: None for regular tables, Some(AlterTableType) for Iceberg or Dynamic tables
+ pub table_type: Option<AlterTableType>,
/// Token that represents the end of the statement (semicolon or EOF)
pub end_token: AttachedToken,
}
impl fmt::Display for AlterTable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- if self.iceberg {
- write!(f, "ALTER ICEBERG TABLE ")?;
- } else {
- write!(f, "ALTER TABLE ")?;
+ match &self.table_type {
+ Some(AlterTableType::Iceberg) => write!(f, "ALTER ICEBERG TABLE ")?,
+ Some(AlterTableType::Dynamic) => write!(f, "ALTER DYNAMIC TABLE ")?,
+ None => write!(f, "ALTER TABLE ")?,
}
if self.if_exists {
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index 4636e4b..b32697f 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -61,7 +61,7 @@
pub use self::ddl::{
AlterColumnOperation, AlterConnectorOwner, AlterIndexOperation, AlterPolicyOperation,
AlterSchema, AlterSchemaOperation, AlterTable, AlterTableAlgorithm, AlterTableLock,
- AlterTableOperation, AlterType, AlterTypeAddValue, AlterTypeAddValuePosition,
+ AlterTableOperation, AlterTableType, AlterType, AlterTypeAddValue, AlterTypeAddValuePosition,
AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue, ClusteredBy, ColumnDef,
ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy, ColumnPolicyProperty,
ConstraintCharacteristics, CreateConnector, CreateDomain, CreateExtension, CreateFunction,
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index 34edabd..80244e6 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -1108,6 +1108,9 @@
AlterTableOperation::DropClusteringKey => Span::empty(),
AlterTableOperation::SuspendRecluster => Span::empty(),
AlterTableOperation::ResumeRecluster => Span::empty(),
+ AlterTableOperation::Refresh => Span::empty(),
+ AlterTableOperation::Suspend => Span::empty(),
+ AlterTableOperation::Resume => Span::empty(),
AlterTableOperation::Algorithm { .. } => Span::empty(),
AlterTableOperation::AutoIncrement { value, .. } => value.span(),
AlterTableOperation::Lock { .. } => Span::empty(),
diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs
index 825fd45..bb0d4f1 100644
--- a/src/dialect/snowflake.rs
+++ b/src/dialect/snowflake.rs
@@ -17,6 +17,7 @@
#[cfg(not(feature = "std"))]
use crate::alloc::string::ToString;
+use crate::ast::helpers::attached_token::AttachedToken;
use crate::ast::helpers::key_value_options::{
KeyValueOption, KeyValueOptionKind, KeyValueOptions, KeyValueOptionsDelimiter,
};
@@ -26,11 +27,12 @@
FileStagingCommand, StageLoadSelectItem, StageLoadSelectItemKind, StageParamsObject,
};
use crate::ast::{
- CatalogSyncNamespaceMode, ColumnOption, ColumnPolicy, ColumnPolicyProperty, ContactEntry,
- CopyIntoSnowflakeKind, CreateTableLikeKind, DollarQuotedString, Ident, IdentityParameters,
- IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder,
- InitializeKind, ObjectName, ObjectNamePart, RefreshModeKind, RowAccessPolicy, ShowObjects,
- SqlOption, Statement, StorageSerializationPolicy, TagsColumnOption, Value, WrappedCollection,
+ AlterTable, AlterTableOperation, AlterTableType, CatalogSyncNamespaceMode, ColumnOption,
+ ColumnPolicy, ColumnPolicyProperty, ContactEntry, CopyIntoSnowflakeKind, CreateTableLikeKind,
+ DollarQuotedString, Ident, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind,
+ IdentityPropertyKind, IdentityPropertyOrder, InitializeKind, ObjectName, ObjectNamePart,
+ RefreshModeKind, RowAccessPolicy, ShowObjects, SqlOption, Statement,
+ StorageSerializationPolicy, TagsColumnOption, Value, WrappedCollection,
};
use crate::dialect::{Dialect, Precedence};
use crate::keywords::Keyword;
@@ -214,6 +216,11 @@
return Some(parser.parse_begin_exception_end());
}
+ if parser.parse_keywords(&[Keyword::ALTER, Keyword::DYNAMIC, Keyword::TABLE]) {
+ // ALTER DYNAMIC TABLE
+ return Some(parse_alter_dynamic_table(parser));
+ }
+
if parser.parse_keywords(&[Keyword::ALTER, Keyword::SESSION]) {
// ALTER SESSION
let set = match parser.parse_one_of_keywords(&[Keyword::SET, Keyword::UNSET]) {
@@ -604,6 +611,44 @@
}
}
+/// Parse snowflake alter dynamic table.
+/// <https://docs.snowflake.com/en/sql-reference/sql/alter-table>
+fn parse_alter_dynamic_table(parser: &mut Parser) -> Result<Statement, ParserError> {
+ // Use parse_object_name(true) to support IDENTIFIER() function
+ let table_name = parser.parse_object_name(true)?;
+
+ // Parse the operation (REFRESH, SUSPEND, or RESUME)
+ let operation = if parser.parse_keyword(Keyword::REFRESH) {
+ AlterTableOperation::Refresh
+ } else if parser.parse_keyword(Keyword::SUSPEND) {
+ AlterTableOperation::Suspend
+ } else if parser.parse_keyword(Keyword::RESUME) {
+ AlterTableOperation::Resume
+ } else {
+ return parser.expected(
+ "REFRESH, SUSPEND, or RESUME after ALTER DYNAMIC 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: false,
+ only: false,
+ operations: vec![operation],
+ location: None,
+ on_cluster: None,
+ table_type: Some(AlterTableType::Dynamic),
+ 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> {
diff --git a/src/keywords.rs b/src/keywords.rs
index 319c578..dc4ecd2 100644
--- a/src/keywords.rs
+++ b/src/keywords.rs
@@ -783,6 +783,7 @@
REF,
REFERENCES,
REFERENCING,
+ REFRESH,
REFRESH_MODE,
REGCLASS,
REGEXP,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index f43329b..026f624 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -9462,7 +9462,11 @@
operations,
location,
on_cluster,
- iceberg,
+ table_type: if iceberg {
+ Some(AlterTableType::Iceberg)
+ } else {
+ None
+ },
end_token: AttachedToken(end_token),
}
.into())
diff --git a/src/test_utils.rs b/src/test_utils.rs
index a8c8afd..b6100d4 100644
--- a/src/test_utils.rs
+++ b/src/test_utils.rs
@@ -347,7 +347,7 @@
assert_eq!(alter_table.name.to_string(), expected_name);
assert!(!alter_table.if_exists);
assert!(!alter_table.only);
- assert!(!alter_table.iceberg);
+ assert_eq!(alter_table.table_type, None);
only(alter_table.operations)
}
_ => panic!("Expected ALTER TABLE statement"),
diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs
index e43df87..86c1013 100644
--- a/tests/sqlparser_mysql.rs
+++ b/tests/sqlparser_mysql.rs
@@ -2746,14 +2746,14 @@
if_exists,
only,
operations,
- iceberg,
+ table_type,
location: _,
on_cluster: _,
end_token: _,
}) => {
assert_eq!(name.to_string(), "tab");
assert!(!if_exists);
- assert!(!iceberg);
+ assert_eq!(table_type, None);
assert!(!only);
assert_eq!(
operations,
diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs
index 2be5eae..f187af1 100644
--- a/tests/sqlparser_snowflake.rs
+++ b/tests/sqlparser_snowflake.rs
@@ -4662,3 +4662,11 @@
snowflake().verified_stmt("ALTER TABLE tbl DROP FOREIGN KEY k1 RESTRICT");
snowflake().verified_stmt("ALTER TABLE tbl DROP CONSTRAINT c1 CASCADE");
}
+
+#[test]
+fn test_alter_dynamic_table() {
+ snowflake().verified_stmt("ALTER DYNAMIC TABLE MY_DYNAMIC_TABLE REFRESH");
+ snowflake().verified_stmt("ALTER DYNAMIC TABLE my_database.my_schema.my_dynamic_table REFRESH");
+ snowflake().verified_stmt("ALTER DYNAMIC TABLE my_dyn_table SUSPEND");
+ snowflake().verified_stmt("ALTER DYNAMIC TABLE my_dyn_table RESUME");
+}