This is an automated email from the ASF dual-hosted git repository.

github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new c439ee94 Add snowflake dynamic table parsing (#2083)
c439ee94 is described below

commit c439ee9419a8b3e91ae6fc406a2fa88bb2bf1bdd
Author: Andriy Romanov <[email protected]>
AuthorDate: Tue Nov 11 00:10:37 2025 -0800

    Add snowflake dynamic table parsing (#2083)
---
 src/ast/ddl.rs               | 48 ++++++++++++++++++++++++++++++++------
 src/ast/mod.rs               |  2 +-
 src/ast/spans.rs             |  3 +++
 src/dialect/snowflake.rs     | 55 ++++++++++++++++++++++++++++++++++++++++----
 src/keywords.rs              |  1 +
 src/parser/mod.rs            |  6 ++++-
 src/test_utils.rs            |  2 +-
 tests/sqlparser_mysql.rs     |  4 ++--
 tests/sqlparser_snowflake.rs |  8 +++++++
 9 files changed, 112 insertions(+), 17 deletions(-)

diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs
index fd481213..1ae24a7f 100644
--- a/src/ast/ddl.rs
+++ b/src/ast/ddl.rs
@@ -365,6 +365,18 @@ pub enum AlterTableOperation {
     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 @@ impl fmt::Display for AlterTableOperation {
                 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 @@ impl Spanned for DropExtension {
     }
 }
 
+/// 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 @@ pub struct AlterTable {
     /// 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 4636e4ba..b32697f6 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -61,7 +61,7 @@ pub use self::dcl::{
 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 34edabd9..80244e69 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -1108,6 +1108,9 @@ impl Spanned for AlterTableOperation {
             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 825fd45f..bb0d4f16 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 @@ use crate::ast::helpers::stmt_data_loading::{
     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 @@ impl Dialect for SnowflakeDialect {
             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 @@ fn parse_file_staging_command(kw: Keyword, parser: &mut 
Parser) -> Result<Statem
     }
 }
 
+/// 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 319c5782..dc4ecd2f 100644
--- a/src/keywords.rs
+++ b/src/keywords.rs
@@ -783,6 +783,7 @@ define_keywords!(
     REF,
     REFERENCES,
     REFERENCING,
+    REFRESH,
     REFRESH_MODE,
     REGCLASS,
     REGEXP,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index f43329be..026f6249 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -9462,7 +9462,11 @@ impl<'a> Parser<'a> {
             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 a8c8afd5..b6100d49 100644
--- a/src/test_utils.rs
+++ b/src/test_utils.rs
@@ -347,7 +347,7 @@ pub fn alter_table_op_with_name(stmt: Statement, 
expected_name: &str) -> AlterTa
             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 e43df87a..86c1013c 100644
--- a/tests/sqlparser_mysql.rs
+++ b/tests/sqlparser_mysql.rs
@@ -2746,14 +2746,14 @@ fn parse_alter_table_add_column() {
             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 2be5eae8..f187af1b 100644
--- a/tests/sqlparser_snowflake.rs
+++ b/tests/sqlparser_snowflake.rs
@@ -4662,3 +4662,11 @@ fn test_drop_constraints() {
     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");
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to