diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index af679d469..d207f5766 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -72,6 +72,21 @@ pub enum AlterTableOperation { if_exists: bool, cascade: bool, }, + /// `ATTACH PART|PARTITION ` + /// Note: this is a ClickHouse-specific operation, please refer to + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/pakrtition#attach-partitionpart) + AttachPartition { + // PART is not a short form of PARTITION, it's a separate keyword + // which represents a physical file on disk and partition is a logical entity. + partition: Partition, + }, + /// `DETACH PART|PARTITION ` + /// Note: this is a ClickHouse-specific operation, please refer to + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#detach-partitionpart) + DetachPartition { + // See `AttachPartition` for more details + partition: Partition, + }, /// `DROP PRIMARY KEY` /// /// Note: this is a MySQL-specific operation. @@ -272,6 +287,12 @@ impl fmt::Display for AlterTableOperation { column_name, if *cascade { " CASCADE" } else { "" } ), + AlterTableOperation::AttachPartition { partition } => { + write!(f, "ATTACH {partition}") + } + AlterTableOperation::DetachPartition { partition } => { + write!(f, "DETACH {partition}") + } AlterTableOperation::EnableAlwaysRule { name } => { write!(f, "ENABLE ALWAYS RULE {name}") } @@ -1305,6 +1326,9 @@ impl fmt::Display for UserDefinedTypeCompositeAttributeDef { pub enum Partition { Identifier(Ident), Expr(Expr), + /// ClickHouse supports PART expr which represents physical partition in disk. + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#attach-partitionpart) + Part(Expr), Partitions(Vec), } @@ -1313,6 +1337,7 @@ impl fmt::Display for Partition { match self { Partition::Identifier(id) => write!(f, "PARTITION ID {id}"), Partition::Expr(expr) => write!(f, "PARTITION {expr}"), + Partition::Part(expr) => write!(f, "PART {expr}"), Partition::Partitions(partitions) => { write!(f, "PARTITION ({})", display_comma_separated(partitions)) } diff --git a/src/keywords.rs b/src/keywords.rs index 49bd969af..c175da874 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -539,6 +539,7 @@ define_keywords!( PARALLEL, PARAMETER, PARQUET, + PART, PARTITION, PARTITIONED, PARTITIONS, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index fe8acb4f2..91717771d 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6540,7 +6540,7 @@ impl<'a> Parser<'a> { } else if dialect_of!(self is PostgreSqlDialect | GenericDialect) && self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) { - let new_owner = match self.parse_one_of_keywords( &[Keyword::CURRENT_USER, Keyword::CURRENT_ROLE, Keyword::SESSION_USER]) { + let new_owner = match self.parse_one_of_keywords(&[Keyword::CURRENT_USER, Keyword::CURRENT_ROLE, Keyword::SESSION_USER]) { Some(Keyword::CURRENT_USER) => Owner::CurrentUser, Some(Keyword::CURRENT_ROLE) => Owner::CurrentRole, Some(Keyword::SESSION_USER) => Owner::SessionUser, @@ -6556,6 +6556,18 @@ impl<'a> Parser<'a> { }; AlterTableOperation::OwnerTo { new_owner } + } else if dialect_of!(self is ClickHouseDialect|GenericDialect) + && self.parse_keyword(Keyword::ATTACH) + { + AlterTableOperation::AttachPartition { + partition: self.parse_part_or_partition()?, + } + } else if dialect_of!(self is ClickHouseDialect|GenericDialect) + && self.parse_keyword(Keyword::DETACH) + { + AlterTableOperation::DetachPartition { + partition: self.parse_part_or_partition()?, + } } else { let options: Vec = self.parse_options_with_keywords(&[Keyword::SET, Keyword::TBLPROPERTIES])?; @@ -6573,6 +6585,16 @@ impl<'a> Parser<'a> { Ok(operation) } + fn parse_part_or_partition(&mut self) -> Result { + let keyword = self.expect_one_of_keywords(&[Keyword::PART, Keyword::PARTITION])?; + match keyword { + Keyword::PART => Ok(Partition::Part(self.parse_expr()?)), + Keyword::PARTITION => Ok(Partition::Expr(self.parse_expr()?)), + // unreachable because expect_one_of_keywords used above + _ => unreachable!(), + } + } + pub fn parse_alter(&mut self) -> Result { let object_type = self.expect_one_of_keywords(&[ Keyword::VIEW, diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index 4108958fb..4676e6e50 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -222,6 +222,71 @@ fn parse_create_table() { ); } +#[test] +fn parse_alter_table_attach_and_detach_partition() { + for operation in &["ATTACH", "DETACH"] { + match clickhouse_and_generic() + .verified_stmt(format!("ALTER TABLE t0 {operation} PARTITION part").as_str()) + { + Statement::AlterTable { + name, operations, .. + } => { + pretty_assertions::assert_eq!("t0", name.to_string()); + pretty_assertions::assert_eq!( + operations[0], + if operation == &"ATTACH" { + AlterTableOperation::AttachPartition { + partition: Partition::Expr(Identifier(Ident::new("part"))), + } + } else { + AlterTableOperation::DetachPartition { + partition: Partition::Expr(Identifier(Ident::new("part"))), + } + } + ); + } + _ => unreachable!(), + } + + match clickhouse_and_generic() + .verified_stmt(format!("ALTER TABLE t1 {operation} PART part").as_str()) + { + Statement::AlterTable { + name, operations, .. + } => { + pretty_assertions::assert_eq!("t1", name.to_string()); + pretty_assertions::assert_eq!( + operations[0], + if operation == &"ATTACH" { + AlterTableOperation::AttachPartition { + partition: Partition::Part(Identifier(Ident::new("part"))), + } + } else { + AlterTableOperation::DetachPartition { + partition: Partition::Part(Identifier(Ident::new("part"))), + } + } + ); + } + _ => unreachable!(), + } + + // negative cases + assert_eq!( + clickhouse_and_generic() + .parse_sql_statements(format!("ALTER TABLE t0 {operation} PARTITION").as_str()) + .unwrap_err(), + ParserError("Expected: an expression:, found: EOF".to_string()) + ); + assert_eq!( + clickhouse_and_generic() + .parse_sql_statements(format!("ALTER TABLE t0 {operation} PART").as_str()) + .unwrap_err(), + ParserError("Expected: an expression:, found: EOF".to_string()) + ); + } +} + #[test] fn parse_optimize_table() { clickhouse_and_generic().verified_stmt("OPTIMIZE TABLE t0");