mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-06-04 06:10:39 +00:00
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:
@@ -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";
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user