Skip to content

Commit 58460ac

Browse files
git-hulkayman-sigma
authored andcommitted
Add support of ATTACH/DETACH PARTITION for ClickHouse (apache#1362)
1 parent 6deb10f commit 58460ac

File tree

4 files changed

+114
-1
lines changed

4 files changed

+114
-1
lines changed

src/ast/ddl.rs

+25
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,21 @@ pub enum AlterTableOperation {
7272
if_exists: bool,
7373
cascade: bool,
7474
},
75+
/// `ATTACH PART|PARTITION <partition_expr>`
76+
/// Note: this is a ClickHouse-specific operation, please refer to
77+
/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/pakrtition#attach-partitionpart)
78+
AttachPartition {
79+
// PART is not a short form of PARTITION, it's a separate keyword
80+
// which represents a physical file on disk and partition is a logical entity.
81+
partition: Partition,
82+
},
83+
/// `DETACH PART|PARTITION <partition_expr>`
84+
/// Note: this is a ClickHouse-specific operation, please refer to
85+
/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#detach-partitionpart)
86+
DetachPartition {
87+
// See `AttachPartition` for more details
88+
partition: Partition,
89+
},
7590
/// `DROP PRIMARY KEY`
7691
///
7792
/// Note: this is a MySQL-specific operation.
@@ -272,6 +287,12 @@ impl fmt::Display for AlterTableOperation {
272287
column_name,
273288
if *cascade { " CASCADE" } else { "" }
274289
),
290+
AlterTableOperation::AttachPartition { partition } => {
291+
write!(f, "ATTACH {partition}")
292+
}
293+
AlterTableOperation::DetachPartition { partition } => {
294+
write!(f, "DETACH {partition}")
295+
}
275296
AlterTableOperation::EnableAlwaysRule { name } => {
276297
write!(f, "ENABLE ALWAYS RULE {name}")
277298
}
@@ -1305,6 +1326,9 @@ impl fmt::Display for UserDefinedTypeCompositeAttributeDef {
13051326
pub enum Partition {
13061327
Identifier(Ident),
13071328
Expr(Expr),
1329+
/// ClickHouse supports PART expr which represents physical partition in disk.
1330+
/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#attach-partitionpart)
1331+
Part(Expr),
13081332
Partitions(Vec<Expr>),
13091333
}
13101334

@@ -1313,6 +1337,7 @@ impl fmt::Display for Partition {
13131337
match self {
13141338
Partition::Identifier(id) => write!(f, "PARTITION ID {id}"),
13151339
Partition::Expr(expr) => write!(f, "PARTITION {expr}"),
1340+
Partition::Part(expr) => write!(f, "PART {expr}"),
13161341
Partition::Partitions(partitions) => {
13171342
write!(f, "PARTITION ({})", display_comma_separated(partitions))
13181343
}

src/keywords.rs

+1
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ define_keywords!(
539539
PARALLEL,
540540
PARAMETER,
541541
PARQUET,
542+
PART,
542543
PARTITION,
543544
PARTITIONED,
544545
PARTITIONS,

src/parser/mod.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -6446,7 +6446,7 @@ impl<'a> Parser<'a> {
64466446
} else if dialect_of!(self is PostgreSqlDialect | GenericDialect)
64476447
&& self.parse_keywords(&[Keyword::OWNER, Keyword::TO])
64486448
{
6449-
let new_owner = match self.parse_one_of_keywords( &[Keyword::CURRENT_USER, Keyword::CURRENT_ROLE, Keyword::SESSION_USER]) {
6449+
let new_owner = match self.parse_one_of_keywords(&[Keyword::CURRENT_USER, Keyword::CURRENT_ROLE, Keyword::SESSION_USER]) {
64506450
Some(Keyword::CURRENT_USER) => Owner::CurrentUser,
64516451
Some(Keyword::CURRENT_ROLE) => Owner::CurrentRole,
64526452
Some(Keyword::SESSION_USER) => Owner::SessionUser,
@@ -6462,6 +6462,18 @@ impl<'a> Parser<'a> {
64626462
};
64636463

64646464
AlterTableOperation::OwnerTo { new_owner }
6465+
} else if dialect_of!(self is ClickHouseDialect|GenericDialect)
6466+
&& self.parse_keyword(Keyword::ATTACH)
6467+
{
6468+
AlterTableOperation::AttachPartition {
6469+
partition: self.parse_part_or_partition()?,
6470+
}
6471+
} else if dialect_of!(self is ClickHouseDialect|GenericDialect)
6472+
&& self.parse_keyword(Keyword::DETACH)
6473+
{
6474+
AlterTableOperation::DetachPartition {
6475+
partition: self.parse_part_or_partition()?,
6476+
}
64656477
} else {
64666478
let options: Vec<SqlOption> =
64676479
self.parse_options_with_keywords(&[Keyword::SET, Keyword::TBLPROPERTIES])?;
@@ -6479,6 +6491,16 @@ impl<'a> Parser<'a> {
64796491
Ok(operation)
64806492
}
64816493

6494+
fn parse_part_or_partition(&mut self) -> Result<Partition, ParserError> {
6495+
let keyword = self.expect_one_of_keywords(&[Keyword::PART, Keyword::PARTITION])?;
6496+
match keyword {
6497+
Keyword::PART => Ok(Partition::Part(self.parse_expr()?)),
6498+
Keyword::PARTITION => Ok(Partition::Expr(self.parse_expr()?)),
6499+
// unreachable because expect_one_of_keywords used above
6500+
_ => unreachable!(),
6501+
}
6502+
}
6503+
64826504
pub fn parse_alter(&mut self) -> Result<Statement, ParserError> {
64836505
let object_type = self.expect_one_of_keywords(&[
64846506
Keyword::VIEW,

tests/sqlparser_clickhouse.rs

+65
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,71 @@ fn parse_create_table() {
222222
);
223223
}
224224

225+
#[test]
226+
fn parse_alter_table_attach_and_detach_partition() {
227+
for operation in &["ATTACH", "DETACH"] {
228+
match clickhouse_and_generic()
229+
.verified_stmt(format!("ALTER TABLE t0 {operation} PARTITION part").as_str())
230+
{
231+
Statement::AlterTable {
232+
name, operations, ..
233+
} => {
234+
pretty_assertions::assert_eq!("t0", name.to_string());
235+
pretty_assertions::assert_eq!(
236+
operations[0],
237+
if operation == &"ATTACH" {
238+
AlterTableOperation::AttachPartition {
239+
partition: Partition::Expr(Identifier(Ident::new("part"))),
240+
}
241+
} else {
242+
AlterTableOperation::DetachPartition {
243+
partition: Partition::Expr(Identifier(Ident::new("part"))),
244+
}
245+
}
246+
);
247+
}
248+
_ => unreachable!(),
249+
}
250+
251+
match clickhouse_and_generic()
252+
.verified_stmt(format!("ALTER TABLE t1 {operation} PART part").as_str())
253+
{
254+
Statement::AlterTable {
255+
name, operations, ..
256+
} => {
257+
pretty_assertions::assert_eq!("t1", name.to_string());
258+
pretty_assertions::assert_eq!(
259+
operations[0],
260+
if operation == &"ATTACH" {
261+
AlterTableOperation::AttachPartition {
262+
partition: Partition::Part(Identifier(Ident::new("part"))),
263+
}
264+
} else {
265+
AlterTableOperation::DetachPartition {
266+
partition: Partition::Part(Identifier(Ident::new("part"))),
267+
}
268+
}
269+
);
270+
}
271+
_ => unreachable!(),
272+
}
273+
274+
// negative cases
275+
assert_eq!(
276+
clickhouse_and_generic()
277+
.parse_sql_statements(format!("ALTER TABLE t0 {operation} PARTITION").as_str())
278+
.unwrap_err(),
279+
ParserError("Expected: an expression:, found: EOF".to_string())
280+
);
281+
assert_eq!(
282+
clickhouse_and_generic()
283+
.parse_sql_statements(format!("ALTER TABLE t0 {operation} PART").as_str())
284+
.unwrap_err(),
285+
ParserError("Expected: an expression:, found: EOF".to_string())
286+
);
287+
}
288+
}
289+
225290
#[test]
226291
fn parse_optimize_table() {
227292
clickhouse_and_generic().verified_stmt("OPTIMIZE TABLE t0");

0 commit comments

Comments
 (0)