feat: support ALTER TABLE ... MODIFY COLUMN ... ... (#3796)

* feat: support `ALTER COLUMN xxx TYPE xxx`

* fix: test `test_parse_alter_change_column_type`

* style: code fmt

* style: move to new test: `test_make_alter_column_type_region_request`

* style: simplify the code

* style: remove `v1::region::ChangeColumnType`

* resolve conflicts

* fix: test `test_make_alter_column_type_region_request`

* style: simplify the code

* rebase

* rebase

* rebase

* fix: `ALTER COLUMN ... TYPE` -> `MODIFY COLUMN`

* fix: `parser` -> `self.parser`

* Apply suggestions from code review

---------

Co-authored-by: Ruihang Xia <waynestxia@gmail.com>
Co-authored-by: WenyXu <wenymedia@gmail.com>
This commit is contained in:
Kould
2024-04-30 11:13:33 +08:00
committed by GitHub
parent 371d4cf9f5
commit aba5e41799
10 changed files with 492 additions and 39 deletions

View File

@@ -30,24 +30,24 @@ impl<'a> ParserContext<'a> {
}
fn parse_alter_table(&mut self) -> std::result::Result<AlterTable, ParserError> {
let parser = &mut self.parser;
parser.expect_keywords(&[Keyword::ALTER, Keyword::TABLE])?;
self.parser
.expect_keywords(&[Keyword::ALTER, Keyword::TABLE])?;
let raw_table_name = parser.parse_object_name(false)?;
let raw_table_name = self.parser.parse_object_name(false)?;
let table_name = Self::canonicalize_object_name(raw_table_name);
let alter_operation = if parser.parse_keyword(Keyword::ADD) {
if let Some(constraint) = parser.parse_optional_table_constraint()? {
let alter_operation = if self.parser.parse_keyword(Keyword::ADD) {
if let Some(constraint) = self.parser.parse_optional_table_constraint()? {
AlterTableOperation::AddConstraint(constraint)
} else {
let _ = parser.parse_keyword(Keyword::COLUMN);
let mut column_def = parser.parse_column_def()?;
let _ = self.parser.parse_keyword(Keyword::COLUMN);
let mut column_def = self.parser.parse_column_def()?;
column_def.name = Self::canonicalize_identifier(column_def.name);
let location = if parser.parse_keyword(Keyword::FIRST) {
let location = if self.parser.parse_keyword(Keyword::FIRST) {
Some(AddColumnLocation::First)
} else if let Token::Word(word) = parser.peek_token().token {
} else if let Token::Word(word) = self.parser.peek_token().token {
if word.value.to_ascii_uppercase() == "AFTER" {
let _ = parser.next_token();
let _ = self.parser.next_token();
let name = Self::canonicalize_identifier(self.parse_identifier()?);
Some(AddColumnLocation::After {
column_name: name.value,
@@ -63,17 +63,26 @@ impl<'a> ParserContext<'a> {
location,
}
}
} else if parser.parse_keyword(Keyword::DROP) {
if parser.parse_keyword(Keyword::COLUMN) {
} else if self.parser.parse_keyword(Keyword::DROP) {
if self.parser.parse_keyword(Keyword::COLUMN) {
let name = Self::canonicalize_identifier(self.parse_identifier()?);
AlterTableOperation::DropColumn { name }
} else {
return Err(ParserError::ParserError(format!(
"expect keyword COLUMN after ALTER TABLE DROP, found {}",
parser.peek_token()
self.parser.peek_token()
)));
}
} else if parser.parse_keyword(Keyword::RENAME) {
} else if self.consume_token("MODIFY") {
let _ = self.parser.parse_keyword(Keyword::COLUMN);
let column_name = Self::canonicalize_identifier(self.parser.parse_identifier(false)?);
let target_type = self.parser.parse_data_type()?;
AlterTableOperation::ChangeColumnType {
column_name,
target_type,
}
} else if self.parser.parse_keyword(Keyword::RENAME) {
let new_table_name_obj_raw = self.parse_object_name()?;
let new_table_name_obj = Self::canonicalize_object_name(new_table_name_obj_raw);
let new_table_name = match &new_table_name_obj.0[..] {
@@ -87,8 +96,8 @@ impl<'a> ParserContext<'a> {
AlterTableOperation::RenameTable { new_table_name }
} else {
return Err(ParserError::ParserError(format!(
"expect keyword ADD or DROP or RENAME after ALTER TABLE, found {}",
parser.peek_token()
"expect keyword ADD or DROP or MODIFY or RENAME after ALTER TABLE, found {}",
self.parser.peek_token()
)));
};
Ok(AlterTable::new(table_name, alter_operation))
@@ -253,6 +262,52 @@ mod tests {
}
}
#[test]
fn test_parse_alter_change_column_type() {
let sql_1 = "ALTER TABLE my_metric_1 MODIFY COLUMN a STRING";
let result_1 = ParserContext::create_with_dialect(
sql_1,
&GreptimeDbDialect {},
ParseOptions::default(),
)
.unwrap();
let sql_2 = "ALTER TABLE my_metric_1 MODIFY a STRING";
let mut result_2 = ParserContext::create_with_dialect(
sql_2,
&GreptimeDbDialect {},
ParseOptions::default(),
)
.unwrap();
assert_eq!(result_1, result_2);
assert_eq!(1, result_2.len());
let statement = result_2.remove(0);
assert_matches!(statement, Statement::Alter { .. });
match statement {
Statement::Alter(alter_table) => {
assert_eq!("my_metric_1", alter_table.table_name().0[0].value);
let alter_operation = alter_table.alter_operation();
assert_matches!(
alter_operation,
AlterTableOperation::ChangeColumnType { .. }
);
match alter_operation {
AlterTableOperation::ChangeColumnType {
column_name,
target_type,
} => {
assert_eq!("a", column_name.value);
assert_eq!(DataType::String(None), *target_type);
}
_ => unreachable!(),
}
}
_ => unreachable!(),
}
}
#[test]
fn test_parse_alter_rename_table() {
let sql = "ALTER TABLE test_table table_t";
@@ -260,7 +315,7 @@ mod tests {
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap_err();
let err = result.output_msg();
assert!(err.contains("expect keyword ADD or DROP or RENAME after ALTER TABLE"));
assert!(err.contains("expect keyword ADD or DROP or MODIFY or RENAME after ALTER TABLE"));
let sql = "ALTER TABLE test_table RENAME table_t";
let mut result =

View File

@@ -15,7 +15,7 @@
use std::fmt::{Debug, Display};
use common_query::AddColumnLocation;
use sqlparser::ast::{ColumnDef, Ident, ObjectName, TableConstraint};
use sqlparser::ast::{ColumnDef, DataType, Ident, ObjectName, TableConstraint};
use sqlparser_derive::{Visit, VisitMut};
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut)]
@@ -58,6 +58,11 @@ pub enum AlterTableOperation {
column_def: ColumnDef,
location: Option<AddColumnLocation>,
},
/// `MODIFY <column_name> [target_type]`
ChangeColumnType {
column_name: Ident,
target_type: DataType,
},
/// `DROP COLUMN <name>`
DropColumn { name: Ident },
/// `RENAME <new_table_name>`
@@ -82,6 +87,12 @@ impl Display for AlterTableOperation {
AlterTableOperation::RenameTable { new_table_name } => {
write!(f, r#"RENAME {new_table_name}"#)
}
AlterTableOperation::ChangeColumnType {
column_name,
target_type,
} => {
write!(f, r#"MODIFY COLUMN {column_name} {target_type}"#)
}
}
}
}
@@ -117,6 +128,27 @@ ALTER TABLE monitor ADD COLUMN app STRING DEFAULT 'shop' PRIMARY KEY"#,
}
}
let sql = r"alter table monitor modify column load_15 string;";
let stmts =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap();
assert_eq!(1, stmts.len());
assert_matches!(&stmts[0], Statement::Alter { .. });
match &stmts[0] {
Statement::Alter(set) => {
let new_sql = format!("\n{}", set);
assert_eq!(
r#"
ALTER TABLE monitor MODIFY COLUMN load_15 STRING"#,
&new_sql
);
}
_ => {
unreachable!();
}
}
let sql = r"alter table monitor drop column load_15;";
let stmts =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())