Add support of ATTACH/DETACH PARTITION for ClickHouse (#1362)

This commit is contained in:
hulk 2024-08-08 02:02:11 +08:00 committed by GitHub
parent da484c57c4
commit dfb8b81630
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 114 additions and 1 deletions

View file

@ -72,6 +72,21 @@ pub enum AlterTableOperation {
if_exists: bool, if_exists: bool,
cascade: bool, cascade: bool,
}, },
/// `ATTACH PART|PARTITION <partition_expr>`
/// 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 <partition_expr>`
/// 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` /// `DROP PRIMARY KEY`
/// ///
/// Note: this is a MySQL-specific operation. /// Note: this is a MySQL-specific operation.
@ -272,6 +287,12 @@ impl fmt::Display for AlterTableOperation {
column_name, column_name,
if *cascade { " CASCADE" } else { "" } if *cascade { " CASCADE" } else { "" }
), ),
AlterTableOperation::AttachPartition { partition } => {
write!(f, "ATTACH {partition}")
}
AlterTableOperation::DetachPartition { partition } => {
write!(f, "DETACH {partition}")
}
AlterTableOperation::EnableAlwaysRule { name } => { AlterTableOperation::EnableAlwaysRule { name } => {
write!(f, "ENABLE ALWAYS RULE {name}") write!(f, "ENABLE ALWAYS RULE {name}")
} }
@ -1305,6 +1326,9 @@ impl fmt::Display for UserDefinedTypeCompositeAttributeDef {
pub enum Partition { pub enum Partition {
Identifier(Ident), Identifier(Ident),
Expr(Expr), 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<Expr>), Partitions(Vec<Expr>),
} }
@ -1313,6 +1337,7 @@ impl fmt::Display for Partition {
match self { match self {
Partition::Identifier(id) => write!(f, "PARTITION ID {id}"), Partition::Identifier(id) => write!(f, "PARTITION ID {id}"),
Partition::Expr(expr) => write!(f, "PARTITION {expr}"), Partition::Expr(expr) => write!(f, "PARTITION {expr}"),
Partition::Part(expr) => write!(f, "PART {expr}"),
Partition::Partitions(partitions) => { Partition::Partitions(partitions) => {
write!(f, "PARTITION ({})", display_comma_separated(partitions)) write!(f, "PARTITION ({})", display_comma_separated(partitions))
} }

View file

@ -539,6 +539,7 @@ define_keywords!(
PARALLEL, PARALLEL,
PARAMETER, PARAMETER,
PARQUET, PARQUET,
PART,
PARTITION, PARTITION,
PARTITIONED, PARTITIONED,
PARTITIONS, PARTITIONS,

View file

@ -6432,7 +6432,7 @@ impl<'a> Parser<'a> {
} else if dialect_of!(self is PostgreSqlDialect | GenericDialect) } else if dialect_of!(self is PostgreSqlDialect | GenericDialect)
&& self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) && 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_USER) => Owner::CurrentUser,
Some(Keyword::CURRENT_ROLE) => Owner::CurrentRole, Some(Keyword::CURRENT_ROLE) => Owner::CurrentRole,
Some(Keyword::SESSION_USER) => Owner::SessionUser, Some(Keyword::SESSION_USER) => Owner::SessionUser,
@ -6448,6 +6448,18 @@ impl<'a> Parser<'a> {
}; };
AlterTableOperation::OwnerTo { new_owner } 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 { } else {
let options: Vec<SqlOption> = let options: Vec<SqlOption> =
self.parse_options_with_keywords(&[Keyword::SET, Keyword::TBLPROPERTIES])?; self.parse_options_with_keywords(&[Keyword::SET, Keyword::TBLPROPERTIES])?;
@ -6465,6 +6477,16 @@ impl<'a> Parser<'a> {
Ok(operation) Ok(operation)
} }
fn parse_part_or_partition(&mut self) -> Result<Partition, ParserError> {
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<Statement, ParserError> { pub fn parse_alter(&mut self) -> Result<Statement, ParserError> {
let object_type = self.expect_one_of_keywords(&[ let object_type = self.expect_one_of_keywords(&[
Keyword::VIEW, Keyword::VIEW,

View file

@ -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] #[test]
fn parse_optimize_table() { fn parse_optimize_table() {
clickhouse_and_generic().verified_stmt("OPTIMIZE TABLE t0"); clickhouse_and_generic().verified_stmt("OPTIMIZE TABLE t0");