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]