feat: support alter table partition syntax (#8177)

* feat(sql): support alter table partition syntax

Signed-off-by: WenyXu <wenymedia@gmail.com>

* feat: support repartition source proto

Signed-off-by: WenyXu <wenymedia@gmail.com>

* chore: update greptime-proto

Signed-off-by: WenyXu <wenymedia@gmail.com>

---------

Signed-off-by: WenyXu <wenymedia@gmail.com>
This commit is contained in:
Weny Xu
2026-05-26 23:06:14 +08:00
committed by GitHub
parent 5943b41067
commit f513b77ccc
8 changed files with 316 additions and 65 deletions

View File

@@ -134,6 +134,7 @@ impl ParserContext<'_> {
self.parse_alter_table_merge_partition()?
} else {
match w.keyword {
Keyword::PARTITION => self.parse_alter_table_partition()?,
Keyword::ADD => self.parse_alter_table_add()?,
Keyword::DROP => {
let _ = self.parser.next_token();
@@ -174,7 +175,7 @@ impl ParserContext<'_> {
AlterTableOperation::SetTableOptions { options }
}
_ => self.expected(
"ADD or DROP or MODIFY or RENAME or SET or REPARTITION or SPLIT or MERGE after ALTER TABLE",
"ADD or DROP or MODIFY or RENAME or SET or UNSET or REPARTITION or SPLIT or MERGE or PARTITION after ALTER TABLE",
self.parser.peek_token(),
)?,
}
@@ -218,6 +219,19 @@ impl ParserContext<'_> {
})
}
fn parse_alter_table_partition(&mut self) -> Result<AlterTableOperation> {
let _ = self.parser.next_token();
let partitions = self.parse_partition_on_columns()?;
if partitions.exprs.is_empty() {
return Err(ParserError::ParserError(
"PARTITION ON COLUMNS requires at least one partition expression".to_string(),
))
.context(error::SyntaxSnafu);
}
Ok(AlterTableOperation::Partition { partitions })
}
fn parse_alter_table_split_partition(&mut self) -> Result<AlterTableOperation> {
let _ = self.parser.next_token();
self.parser
@@ -976,6 +990,100 @@ ALTER TABLE t REPARTITION (
}
}
#[test]
fn test_parse_alter_table_partition_on_columns() {
let sql = r#"
ALTER TABLE sensor_readings PARTITION ON COLUMNS (device_id, area) (
device_id < 100 AND area < 'South',
device_id < 100 AND area >= 'South',
device_id >= 100 AND area <= 'East',
device_id >= 100 AND area > 'East'
);"#;
let mut result =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap();
assert_eq!(1, result.len());
let statement = result.remove(0);
assert_matches!(statement, Statement::AlterTable { .. });
if let Statement::AlterTable(alter_table) = statement {
assert_matches!(
alter_table.alter_operation(),
AlterTableOperation::Partition { .. }
);
if let AlterTableOperation::Partition { partitions } = alter_table.alter_operation() {
assert_eq!(partitions.column_list.len(), 2);
assert_eq!(partitions.column_list[0].value, "device_id");
assert_eq!(partitions.column_list[1].value, "area");
assert_eq!(partitions.exprs.len(), 4);
assert_eq!(
partitions.exprs[0].to_string(),
"device_id < 100 AND area < 'South'"
);
assert_eq!(
partitions.exprs[3].to_string(),
"device_id >= 100 AND area > 'East'"
);
}
}
}
#[test]
fn test_parse_alter_table_partition_on_columns_with_options() {
let sql = r#"
ALTER TABLE sensor_readings PARTITION ON COLUMNS (device_id) (
device_id < 100,
device_id >= 100
) WITH (
TIMEOUT = '5m',
WAIT = false
);"#;
let mut result =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap();
assert_eq!(1, result.len());
let statement = result.remove(0);
assert_matches!(statement, Statement::AlterTable { .. });
if let Statement::AlterTable(alter_table) = statement {
assert_matches!(
alter_table.alter_operation(),
AlterTableOperation::Partition { .. }
);
let options = alter_table.options().to_str_map();
assert_eq!(options.get("timeout").unwrap(), &"5m");
assert_eq!(options.get("wait").unwrap(), &"false");
assert_eq!(options.len(), 2);
}
}
#[test]
fn test_parse_alter_table_partition_on_columns_empty_columns() {
let sql = r#"
ALTER TABLE sensor_readings PARTITION ON COLUMNS () (
device_id < 100
);"#;
let result =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
assert!(result.is_err());
}
#[test]
fn test_parse_alter_table_partition_on_columns_empty_exprs() {
let sql = r#"
ALTER TABLE sensor_readings PARTITION ON COLUMNS (device_id) ();"#;
let result =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap_err();
assert_eq!(
result.output_msg(),
"Invalid SQL syntax: sql parser error: PARTITION ON COLUMNS requires at least one partition expression"
);
}
#[test]
fn test_parse_alter_table_split_partition() {
let sql = r#"
@@ -1274,7 +1382,7 @@ ALTER TABLE metrics REPARTITION
let err = result.output_msg();
assert_eq!(
err,
"Invalid SQL syntax: sql parser error: Expected ADD or DROP or MODIFY or RENAME or SET or REPARTITION or SPLIT or MERGE after ALTER TABLE, found: table_t"
"Invalid SQL syntax: sql parser error: Expected ADD or DROP or MODIFY or RENAME or SET or UNSET or REPARTITION or SPLIT or MERGE or PARTITION after ALTER TABLE, found: table_t"
);
let sql = "ALTER TABLE test_table RENAME table_t";

View File

@@ -502,6 +502,12 @@ impl<'a> ParserContext<'a> {
if !self.parser.parse_keyword(Keyword::PARTITION) {
return Ok(None);
}
self.parse_partition_on_columns().map(Some)
}
/// Parses the "ON COLUMNS (...) (...)" part after "PARTITION".
pub(crate) fn parse_partition_on_columns(&mut self) -> Result<Partitions> {
self.parser
.expect_keywords(&[Keyword::ON, Keyword::COLUMNS])
.context(error::UnexpectedSnafu {
@@ -520,7 +526,7 @@ impl<'a> ParserContext<'a> {
let exprs = self.parse_comma_separated(Self::parse_partition_entry)?;
Ok(Some(Partitions { column_list, exprs }))
Ok(Partitions { column_list, exprs })
}
fn parse_partition_entry(&mut self) -> Result<Expr> {

View File

@@ -26,6 +26,7 @@ use sqlparser::ast::{ColumnDef, DataType, Expr, Ident, ObjectName, TableConstrai
use sqlparser_derive::{Visit, VisitMut};
use crate::statements::OptionMap;
use crate::statements::create::Partitions;
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut, Serialize)]
pub struct AlterTable {
@@ -119,6 +120,10 @@ pub enum AlterTableOperation {
Repartition {
operation: RepartitionOperation,
},
/// `PARTITION ON COLUMNS (...) (...)`
Partition {
partitions: Partitions,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut, Serialize)]
@@ -248,6 +253,9 @@ impl Display for AlterTableOperation {
AlterTableOperation::Repartition { operation } => {
write!(f, "REPARTITION {operation}")
}
AlterTableOperation::Partition { partitions } => {
write!(f, "{partitions}")
}
AlterTableOperation::SetIndex { options } => match options {
SetIndexOperation::Fulltext {
column_name,