Snowflake ALTER TABLE clustering options (#1579)
diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs
index 6c930a4..849b583 100644
--- a/src/ast/ddl.rs
+++ b/src/ast/ddl.rs
@@ -70,7 +70,10 @@
///
/// Note: this is a ClickHouse-specific operation.
/// Please refer to [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/projection#drop-projection)
- DropProjection { if_exists: bool, name: Ident },
+ DropProjection {
+ if_exists: bool,
+ name: Ident,
+ },
/// `MATERIALIZE PROJECTION [IF EXISTS] name [IN PARTITION partition_name]`
///
@@ -99,11 +102,15 @@
/// `DISABLE RULE rewrite_rule_name`
///
/// Note: this is a PostgreSQL-specific operation.
- DisableRule { name: Ident },
+ DisableRule {
+ name: Ident,
+ },
/// `DISABLE TRIGGER [ trigger_name | ALL | USER ]`
///
/// Note: this is a PostgreSQL-specific operation.
- DisableTrigger { name: Ident },
+ DisableTrigger {
+ name: Ident,
+ },
/// `DROP CONSTRAINT [ IF EXISTS ] <name>`
DropConstraint {
if_exists: bool,
@@ -152,19 +159,27 @@
/// `ENABLE ALWAYS RULE rewrite_rule_name`
///
/// Note: this is a PostgreSQL-specific operation.
- EnableAlwaysRule { name: Ident },
+ EnableAlwaysRule {
+ name: Ident,
+ },
/// `ENABLE ALWAYS TRIGGER trigger_name`
///
/// Note: this is a PostgreSQL-specific operation.
- EnableAlwaysTrigger { name: Ident },
+ EnableAlwaysTrigger {
+ name: Ident,
+ },
/// `ENABLE REPLICA RULE rewrite_rule_name`
///
/// Note: this is a PostgreSQL-specific operation.
- EnableReplicaRule { name: Ident },
+ EnableReplicaRule {
+ name: Ident,
+ },
/// `ENABLE REPLICA TRIGGER trigger_name`
///
/// Note: this is a PostgreSQL-specific operation.
- EnableReplicaTrigger { name: Ident },
+ EnableReplicaTrigger {
+ name: Ident,
+ },
/// `ENABLE ROW LEVEL SECURITY`
///
/// Note: this is a PostgreSQL-specific operation.
@@ -172,11 +187,15 @@
/// `ENABLE RULE rewrite_rule_name`
///
/// Note: this is a PostgreSQL-specific operation.
- EnableRule { name: Ident },
+ EnableRule {
+ name: Ident,
+ },
/// `ENABLE TRIGGER [ trigger_name | ALL | USER ]`
///
/// Note: this is a PostgreSQL-specific operation.
- EnableTrigger { name: Ident },
+ EnableTrigger {
+ name: Ident,
+ },
/// `RENAME TO PARTITION (partition=val)`
RenamePartitions {
old_partitions: Vec<Expr>,
@@ -197,7 +216,9 @@
new_column_name: Ident,
},
/// `RENAME TO <table_name>`
- RenameTable { table_name: ObjectName },
+ RenameTable {
+ table_name: ObjectName,
+ },
// CHANGE [ COLUMN ] <old_name> <new_name> <data_type> [ <options> ]
ChangeColumn {
old_name: Ident,
@@ -218,7 +239,10 @@
/// `RENAME CONSTRAINT <old_constraint_name> TO <new_constraint_name>`
///
/// Note: this is a PostgreSQL-specific operation.
- RenameConstraint { old_name: Ident, new_name: Ident },
+ RenameConstraint {
+ old_name: Ident,
+ new_name: Ident,
+ },
/// `ALTER [ COLUMN ]`
AlterColumn {
column_name: Ident,
@@ -227,14 +251,27 @@
/// 'SWAP WITH <table_name>'
///
/// Note: this is Snowflake specific <https://docs.snowflake.com/en/sql-reference/sql/alter-table>
- SwapWith { table_name: ObjectName },
+ SwapWith {
+ table_name: ObjectName,
+ },
/// 'SET TBLPROPERTIES ( { property_key [ = ] property_val } [, ...] )'
- SetTblProperties { table_properties: Vec<SqlOption> },
-
+ SetTblProperties {
+ table_properties: Vec<SqlOption>,
+ },
/// `OWNER TO { <new_owner> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }`
///
/// Note: this is PostgreSQL-specific <https://www.postgresql.org/docs/current/sql-altertable.html>
- OwnerTo { new_owner: Owner },
+ OwnerTo {
+ new_owner: Owner,
+ },
+ /// Snowflake table clustering options
+ /// <https://docs.snowflake.com/en/sql-reference/sql/alter-table#clustering-actions-clusteringaction>
+ ClusterBy {
+ exprs: Vec<Expr>,
+ },
+ DropClusteringKey,
+ SuspendRecluster,
+ ResumeRecluster,
}
/// An `ALTER Policy` (`Statement::AlterPolicy`) operation
@@ -548,6 +585,22 @@
}
Ok(())
}
+ AlterTableOperation::ClusterBy { exprs } => {
+ write!(f, "CLUSTER BY ({})", display_comma_separated(exprs))?;
+ Ok(())
+ }
+ AlterTableOperation::DropClusteringKey => {
+ write!(f, "DROP CLUSTERING KEY")?;
+ Ok(())
+ }
+ AlterTableOperation::SuspendRecluster => {
+ write!(f, "SUSPEND RECLUSTER")?;
+ Ok(())
+ }
+ AlterTableOperation::ResumeRecluster => {
+ write!(f, "RESUME RECLUSTER")?;
+ Ok(())
+ }
}
}
}
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index cd3bda1..de577c9 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -1020,6 +1020,10 @@
union_spans(table_properties.iter().map(|i| i.span()))
}
AlterTableOperation::OwnerTo { .. } => Span::empty(),
+ AlterTableOperation::ClusterBy { exprs } => union_spans(exprs.iter().map(|e| e.span())),
+ AlterTableOperation::DropClusteringKey => Span::empty(),
+ AlterTableOperation::SuspendRecluster => Span::empty(),
+ AlterTableOperation::ResumeRecluster => Span::empty(),
}
}
}
diff --git a/src/keywords.rs b/src/keywords.rs
index bd97c3c..25a719d 100644
--- a/src/keywords.rs
+++ b/src/keywords.rs
@@ -168,6 +168,7 @@
CLOSE,
CLUSTER,
CLUSTERED,
+ CLUSTERING,
COALESCE,
COLLATE,
COLLATION,
@@ -622,6 +623,7 @@
READS,
READ_ONLY,
REAL,
+ RECLUSTER,
RECURSIVE,
REF,
REFERENCES,
@@ -656,6 +658,7 @@
RESTRICTIVE,
RESULT,
RESULTSET,
+ RESUME,
RETAIN,
RETURN,
RETURNING,
@@ -745,6 +748,7 @@
SUM,
SUPER,
SUPERUSER,
+ SUSPEND,
SWAP,
SYMMETRIC,
SYNC,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index b5365b5..ac76f64 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -7273,6 +7273,8 @@
let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
let name = self.parse_identifier(false)?;
AlterTableOperation::DropProjection { if_exists, name }
+ } else if self.parse_keywords(&[Keyword::CLUSTERING, Keyword::KEY]) {
+ AlterTableOperation::DropClusteringKey
} else {
let _ = self.parse_keyword(Keyword::COLUMN); // [ COLUMN ]
let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
@@ -7444,6 +7446,15 @@
partition,
with_name,
}
+ } else if self.parse_keywords(&[Keyword::CLUSTER, Keyword::BY]) {
+ self.expect_token(&Token::LParen)?;
+ let exprs = self.parse_comma_separated(|parser| parser.parse_expr())?;
+ self.expect_token(&Token::RParen)?;
+ AlterTableOperation::ClusterBy { exprs }
+ } else if self.parse_keywords(&[Keyword::SUSPEND, Keyword::RECLUSTER]) {
+ AlterTableOperation::SuspendRecluster
+ } else if self.parse_keywords(&[Keyword::RESUME, Keyword::RECLUSTER]) {
+ AlterTableOperation::ResumeRecluster
} else {
let options: Vec<SqlOption> =
self.parse_options_with_keywords(&[Keyword::SET, Keyword::TBLPROPERTIES])?;
diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs
index fb8a60c..3cbd87b 100644
--- a/tests/sqlparser_snowflake.rs
+++ b/tests/sqlparser_snowflake.rs
@@ -1412,6 +1412,42 @@
}
#[test]
+fn test_alter_table_clustering() {
+ let sql = r#"ALTER TABLE tab CLUSTER BY (c1, "c2", TO_DATE(c3))"#;
+ match alter_table_op(snowflake_and_generic().verified_stmt(sql)) {
+ AlterTableOperation::ClusterBy { exprs } => {
+ assert_eq!(
+ exprs,
+ [
+ Expr::Identifier(Ident::new("c1")),
+ Expr::Identifier(Ident::with_quote('"', "c2")),
+ Expr::Function(Function {
+ name: ObjectName(vec![Ident::new("TO_DATE")]),
+ parameters: FunctionArguments::None,
+ args: FunctionArguments::List(FunctionArgumentList {
+ args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
+ Expr::Identifier(Ident::new("c3"))
+ ))],
+ duplicate_treatment: None,
+ clauses: vec![],
+ }),
+ filter: None,
+ null_treatment: None,
+ over: None,
+ within_group: vec![]
+ })
+ ],
+ );
+ }
+ _ => unreachable!(),
+ }
+
+ snowflake_and_generic().verified_stmt("ALTER TABLE tbl DROP CLUSTERING KEY");
+ snowflake_and_generic().verified_stmt("ALTER TABLE tbl SUSPEND RECLUSTER");
+ snowflake_and_generic().verified_stmt("ALTER TABLE tbl RESUME RECLUSTER");
+}
+
+#[test]
fn test_drop_stage() {
match snowflake_and_generic().verified_stmt("DROP STAGE s1") {
Statement::Drop {