mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-01-09 06:42:57 +00:00
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:
@@ -12,17 +12,18 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use api::helper::ColumnDataTypeWrapper;
|
||||
use api::v1::add_column_location::LocationType;
|
||||
use api::v1::alter_expr::Kind;
|
||||
use api::v1::{
|
||||
column_def, AddColumnLocation as Location, AlterExpr, CreateTableExpr, DropColumns,
|
||||
RenameTable, SemanticType,
|
||||
column_def, AddColumnLocation as Location, AlterExpr, ChangeColumnTypes, CreateTableExpr,
|
||||
DropColumns, RenameTable, SemanticType,
|
||||
};
|
||||
use common_query::AddColumnLocation;
|
||||
use datatypes::schema::{ColumnSchema, RawSchema};
|
||||
use snafu::{ensure, OptionExt, ResultExt};
|
||||
use table::metadata::TableId;
|
||||
use table::requests::{AddColumnRequest, AlterKind, AlterTableRequest};
|
||||
use table::requests::{AddColumnRequest, AlterKind, AlterTableRequest, ChangeColumnTypeRequest};
|
||||
|
||||
use crate::error::{
|
||||
InvalidColumnDefSnafu, MissingFieldSnafu, MissingTimestampColumnSnafu, Result,
|
||||
@@ -64,13 +65,33 @@ pub fn alter_expr_to_request(table_id: TableId, expr: AlterExpr) -> Result<Alter
|
||||
columns: add_column_requests,
|
||||
}
|
||||
}
|
||||
Kind::ChangeColumnTypes(ChangeColumnTypes {
|
||||
change_column_types,
|
||||
}) => {
|
||||
let change_column_type_requests = change_column_types
|
||||
.into_iter()
|
||||
.map(|cct| {
|
||||
let target_type =
|
||||
ColumnDataTypeWrapper::new(cct.target_type(), cct.target_type_extension)
|
||||
.into();
|
||||
|
||||
Ok(ChangeColumnTypeRequest {
|
||||
column_name: cct.column_name,
|
||||
target_type,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
AlterKind::ChangeColumnTypes {
|
||||
columns: change_column_type_requests,
|
||||
}
|
||||
}
|
||||
Kind::DropColumns(DropColumns { drop_columns }) => AlterKind::DropColumns {
|
||||
names: drop_columns.into_iter().map(|c| c.name).collect(),
|
||||
},
|
||||
Kind::RenameTable(RenameTable { new_table_name }) => {
|
||||
AlterKind::RenameTable { new_table_name }
|
||||
}
|
||||
Kind::ChangeColumnTypes(_) => unimplemented!(),
|
||||
};
|
||||
|
||||
let request = AlterTableRequest {
|
||||
@@ -138,7 +159,10 @@ fn parse_location(location: Option<Location>) -> Result<Option<AddColumnLocation
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use api::v1::{AddColumn, AddColumns, ColumnDataType, ColumnDef, DropColumn, SemanticType};
|
||||
use api::v1::{
|
||||
AddColumn, AddColumns, ChangeColumnType, ColumnDataType, ColumnDef, DropColumn,
|
||||
SemanticType,
|
||||
};
|
||||
use datatypes::prelude::ConcreteDataType;
|
||||
|
||||
use super::*;
|
||||
@@ -261,6 +285,40 @@ mod tests {
|
||||
assert_eq!(Some(AddColumnLocation::First), add_column.location);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_change_column_type_expr() {
|
||||
let expr = AlterExpr {
|
||||
catalog_name: "test_catalog".to_string(),
|
||||
schema_name: "test_schema".to_string(),
|
||||
table_name: "monitor".to_string(),
|
||||
|
||||
kind: Some(Kind::ChangeColumnTypes(ChangeColumnTypes {
|
||||
change_column_types: vec![ChangeColumnType {
|
||||
column_name: "mem_usage".to_string(),
|
||||
target_type: ColumnDataType::String as i32,
|
||||
target_type_extension: None,
|
||||
}],
|
||||
})),
|
||||
};
|
||||
|
||||
let alter_request = alter_expr_to_request(1, expr).unwrap();
|
||||
assert_eq!(alter_request.catalog_name, "test_catalog");
|
||||
assert_eq!(alter_request.schema_name, "test_schema");
|
||||
assert_eq!("monitor".to_string(), alter_request.table_name);
|
||||
|
||||
let mut change_column_types = match alter_request.alter_kind {
|
||||
AlterKind::ChangeColumnTypes { columns } => columns,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let change_column_type = change_column_types.pop().unwrap();
|
||||
assert_eq!("mem_usage", change_column_type.column_name);
|
||||
assert_eq!(
|
||||
ConcreteDataType::string_datatype(),
|
||||
change_column_type.target_type
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drop_column_expr() {
|
||||
let expr = AlterExpr {
|
||||
|
||||
@@ -91,6 +91,7 @@ fn create_proto_alter_kind(
|
||||
add_columns,
|
||||
})))
|
||||
}
|
||||
Kind::ChangeColumnTypes(x) => Ok(Some(alter_request::Kind::ChangeColumnTypes(x.clone()))),
|
||||
Kind::DropColumns(x) => {
|
||||
let drop_columns = x
|
||||
.drop_columns
|
||||
@@ -105,7 +106,6 @@ fn create_proto_alter_kind(
|
||||
})))
|
||||
}
|
||||
Kind::RenameTable(_) => Ok(None),
|
||||
Kind::ChangeColumnTypes(_) => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,27 +119,27 @@ mod tests {
|
||||
use api::v1::region::region_request::Body;
|
||||
use api::v1::region::RegionColumnDef;
|
||||
use api::v1::{
|
||||
region, AddColumn, AddColumnLocation, AddColumns, AlterExpr, ColumnDataType,
|
||||
ColumnDef as PbColumnDef, SemanticType,
|
||||
region, AddColumn, AddColumnLocation, AddColumns, AlterExpr, ChangeColumnType,
|
||||
ChangeColumnTypes, ColumnDataType, ColumnDef as PbColumnDef, SemanticType,
|
||||
};
|
||||
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||
use store_api::storage::RegionId;
|
||||
use store_api::storage::{RegionId, TableId};
|
||||
|
||||
use crate::ddl::alter_table::AlterTableProcedure;
|
||||
use crate::ddl::test_util::columns::TestColumnDefBuilder;
|
||||
use crate::ddl::test_util::create_table::{
|
||||
build_raw_table_info_from_expr, TestCreateTableExprBuilder,
|
||||
};
|
||||
use crate::ddl::DdlContext;
|
||||
use crate::key::table_route::TableRouteValue;
|
||||
use crate::peer::Peer;
|
||||
use crate::rpc::ddl::AlterTableTask;
|
||||
use crate::rpc::router::{Region, RegionRoute};
|
||||
use crate::test_util::{new_ddl_context, MockDatanodeManager};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_make_alter_region_request() {
|
||||
let node_manager = Arc::new(MockDatanodeManager::new(()));
|
||||
let ddl_context = new_ddl_context(node_manager);
|
||||
async fn prepare_ddl_context() -> (DdlContext, u64, TableId, RegionId, String) {
|
||||
let datanode_manager = Arc::new(MockDatanodeManager::new(()));
|
||||
let ddl_context = new_ddl_context(datanode_manager);
|
||||
let cluster_id = 1;
|
||||
let table_id = 1024;
|
||||
let region_id = RegionId::new(table_id, 1);
|
||||
@@ -194,12 +194,25 @@ mod tests {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
(
|
||||
ddl_context,
|
||||
cluster_id,
|
||||
table_id,
|
||||
region_id,
|
||||
table_name.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_make_alter_region_request() {
|
||||
let (ddl_context, cluster_id, table_id, region_id, table_name) =
|
||||
prepare_ddl_context().await;
|
||||
|
||||
let task = AlterTableTask {
|
||||
alter_table: AlterExpr {
|
||||
catalog_name: DEFAULT_CATALOG_NAME.to_string(),
|
||||
schema_name: DEFAULT_SCHEMA_NAME.to_string(),
|
||||
table_name: table_name.to_string(),
|
||||
table_name,
|
||||
kind: Some(Kind::AddColumns(AddColumns {
|
||||
add_columns: vec![AddColumn {
|
||||
column_def: Some(PbColumnDef {
|
||||
@@ -256,4 +269,48 @@ mod tests {
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_make_alter_column_type_region_request() {
|
||||
let (ddl_context, cluster_id, table_id, region_id, table_name) =
|
||||
prepare_ddl_context().await;
|
||||
|
||||
let task = AlterTableTask {
|
||||
alter_table: AlterExpr {
|
||||
catalog_name: DEFAULT_CATALOG_NAME.to_string(),
|
||||
schema_name: DEFAULT_SCHEMA_NAME.to_string(),
|
||||
table_name,
|
||||
kind: Some(Kind::ChangeColumnTypes(ChangeColumnTypes {
|
||||
change_column_types: vec![ChangeColumnType {
|
||||
column_name: "cpu".to_string(),
|
||||
target_type: ColumnDataType::String as i32,
|
||||
target_type_extension: None,
|
||||
}],
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
let mut procedure =
|
||||
AlterTableProcedure::new(cluster_id, table_id, task, ddl_context).unwrap();
|
||||
procedure.on_prepare().await.unwrap();
|
||||
let Some(Body::Alter(alter_region_request)) =
|
||||
procedure.make_alter_region_request(region_id).unwrap().body
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
assert_eq!(alter_region_request.region_id, region_id.as_u64());
|
||||
assert_eq!(alter_region_request.schema_version, 1);
|
||||
assert_eq!(
|
||||
alter_region_request.kind,
|
||||
Some(region::alter_request::Kind::ChangeColumnTypes(
|
||||
ChangeColumnTypes {
|
||||
change_column_types: vec![ChangeColumnType {
|
||||
column_name: "cpu".to_string(),
|
||||
target_type: ColumnDataType::String as i32,
|
||||
target_type_extension: None,
|
||||
}]
|
||||
}
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ use std::collections::{HashMap, HashSet};
|
||||
use api::helper::ColumnDataTypeWrapper;
|
||||
use api::v1::alter_expr::Kind;
|
||||
use api::v1::{
|
||||
AddColumn, AddColumns, AlterExpr, Column, ColumnDataType, ColumnDataTypeExtension,
|
||||
CreateTableExpr, DropColumn, DropColumns, RenameTable, SemanticType,
|
||||
AddColumn, AddColumns, AlterExpr, ChangeColumnType, ChangeColumnTypes, Column, ColumnDataType,
|
||||
ColumnDataTypeExtension, CreateTableExpr, DropColumn, DropColumns, RenameTable, SemanticType,
|
||||
};
|
||||
use common_error::ext::BoxedError;
|
||||
use common_grpc_expr::util::ColumnExpr;
|
||||
@@ -38,7 +38,9 @@ use snafu::{ensure, OptionExt, ResultExt};
|
||||
use sql::ast::{ColumnDef, ColumnOption, TableConstraint};
|
||||
use sql::statements::alter::{AlterTable, AlterTableOperation};
|
||||
use sql::statements::create::{CreateExternalTable, CreateFlow, CreateTable, TIME_INDEX};
|
||||
use sql::statements::{column_def_to_schema, sql_column_def_to_grpc_column_def};
|
||||
use sql::statements::{
|
||||
column_def_to_schema, sql_column_def_to_grpc_column_def, sql_data_type_to_concrete_data_type,
|
||||
};
|
||||
use sql::util::extract_tables_from_query;
|
||||
use table::requests::{TableOptions, FILE_TABLE_META_KEY};
|
||||
use table::table_reference::TableReference;
|
||||
@@ -474,6 +476,23 @@ pub(crate) fn to_alter_expr(
|
||||
location: location.as_ref().map(From::from),
|
||||
}],
|
||||
}),
|
||||
AlterTableOperation::ChangeColumnType {
|
||||
column_name,
|
||||
target_type,
|
||||
} => {
|
||||
let target_type =
|
||||
sql_data_type_to_concrete_data_type(target_type).context(ParseSqlSnafu)?;
|
||||
let (target_type, target_type_extension) = ColumnDataTypeWrapper::try_from(target_type)
|
||||
.map(|w| w.to_parts())
|
||||
.context(ColumnDataTypeSnafu)?;
|
||||
Kind::ChangeColumnTypes(ChangeColumnTypes {
|
||||
change_column_types: vec![ChangeColumnType {
|
||||
column_name: column_name.value.to_string(),
|
||||
target_type: target_type as i32,
|
||||
target_type_extension,
|
||||
}],
|
||||
})
|
||||
}
|
||||
AlterTableOperation::DropColumn { name } => Kind::DropColumns(DropColumns {
|
||||
drop_columns: vec![DropColumn {
|
||||
name: name.value.to_string(),
|
||||
@@ -709,4 +728,39 @@ mod tests {
|
||||
if ts.to_iso8601_string() == "2024-01-29 16:01:01+0000")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_alter_change_column_type_expr() {
|
||||
let sql = "ALTER TABLE monitor MODIFY mem_usage STRING;";
|
||||
let stmt =
|
||||
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
|
||||
.unwrap()
|
||||
.pop()
|
||||
.unwrap();
|
||||
|
||||
let Statement::Alter(alter_table) = stmt else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
// query context with system timezone UTC.
|
||||
let expr = to_alter_expr(alter_table.clone(), QueryContext::arc()).unwrap();
|
||||
let kind = expr.kind.unwrap();
|
||||
|
||||
let Kind::ChangeColumnTypes(ChangeColumnTypes {
|
||||
change_column_types,
|
||||
}) = kind
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
assert_eq!(1, change_column_types.len());
|
||||
let change_column_type = &change_column_types[0];
|
||||
|
||||
assert_eq!("mem_usage", change_column_type.column_name);
|
||||
assert_eq!(
|
||||
ColumnDataType::String as i32,
|
||||
change_column_type.target_type
|
||||
);
|
||||
assert!(change_column_type.target_type_extension.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
// limitations under the License.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{self};
|
||||
use std::fmt;
|
||||
|
||||
use api::helper::ColumnDataTypeWrapper;
|
||||
use api::v1::add_column_location::LocationType;
|
||||
use api::v1::region::{
|
||||
alter_request, region_request, AlterRequest, AlterRequests, CloseRequest, CompactRequest,
|
||||
@@ -457,13 +458,18 @@ impl TryFrom<alter_request::Kind> for AlterKind {
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
AlterKind::AddColumns { columns }
|
||||
}
|
||||
alter_request::Kind::ChangeColumnTypes(x) => {
|
||||
let columns = x
|
||||
.change_column_types
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect::<Vec<_>>();
|
||||
AlterKind::ChangeColumnTypes { columns }
|
||||
}
|
||||
alter_request::Kind::DropColumns(x) => {
|
||||
let names = x.drop_columns.into_iter().map(|x| x.name).collect();
|
||||
AlterKind::DropColumns { names }
|
||||
}
|
||||
alter_request::Kind::ChangeColumnTypes(_) => {
|
||||
unimplemented!()
|
||||
}
|
||||
};
|
||||
|
||||
Ok(alter_kind)
|
||||
@@ -615,6 +621,21 @@ impl ChangeColumnType {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v1::ChangeColumnType> for ChangeColumnType {
|
||||
fn from(change_column_type: v1::ChangeColumnType) -> Self {
|
||||
let target_type = ColumnDataTypeWrapper::new(
|
||||
change_column_type.target_type(),
|
||||
change_column_type.target_type_extension,
|
||||
)
|
||||
.into();
|
||||
|
||||
ChangeColumnType {
|
||||
column_name: change_column_type.column_name,
|
||||
target_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct RegionFlushRequest {
|
||||
pub row_group_size: Option<usize>,
|
||||
|
||||
91
tests/cases/standalone/common/alter/change_col_type.result
Normal file
91
tests/cases/standalone/common/alter/change_col_type.result
Normal file
@@ -0,0 +1,91 @@
|
||||
CREATE TABLE test(id INTEGER PRIMARY KEY, i INTEGER NULL, j TIMESTAMP TIME INDEX, k BOOLEAN);
|
||||
|
||||
Affected Rows: 0
|
||||
|
||||
INSERT INTO test VALUES (1, 1, 1, false), (2, 2, 2, true);
|
||||
|
||||
Affected Rows: 2
|
||||
|
||||
ALTER TABLE test MODIFY "I" STRING;
|
||||
|
||||
Error: 4002(TableColumnNotFound), Column I not exists in table test
|
||||
|
||||
ALTER TABLE test MODIFY k DATE;
|
||||
|
||||
Error: 1004(InvalidArguments), Invalid alter table(test) request: column 'k' cannot be cast automatically to type 'Date'
|
||||
|
||||
ALTER TABLE test MODIFY id STRING;
|
||||
|
||||
Error: 1004(InvalidArguments), Invalid alter table(test) request: Not allowed to change primary key index column 'id'
|
||||
|
||||
ALTER TABLE test MODIFY j STRING;
|
||||
|
||||
Error: 1004(InvalidArguments), Invalid alter table(test) request: Not allowed to change timestamp index column 'j' datatype
|
||||
|
||||
ALTER TABLE test MODIFY I STRING;
|
||||
|
||||
Affected Rows: 0
|
||||
|
||||
SELECT * FROM test;
|
||||
|
||||
+----+---+-------------------------+-------+
|
||||
| id | i | j | k |
|
||||
+----+---+-------------------------+-------+
|
||||
| 1 | 1 | 1970-01-01T00:00:00.001 | false |
|
||||
| 2 | 2 | 1970-01-01T00:00:00.002 | true |
|
||||
+----+---+-------------------------+-------+
|
||||
|
||||
INSERT INTO test VALUES (3, "greptime", 3, true);
|
||||
|
||||
Affected Rows: 1
|
||||
|
||||
SELECT * FROM test;
|
||||
|
||||
+----+----------+-------------------------+-------+
|
||||
| id | i | j | k |
|
||||
+----+----------+-------------------------+-------+
|
||||
| 1 | 1 | 1970-01-01T00:00:00.001 | false |
|
||||
| 2 | 2 | 1970-01-01T00:00:00.002 | true |
|
||||
| 3 | greptime | 1970-01-01T00:00:00.003 | true |
|
||||
+----+----------+-------------------------+-------+
|
||||
|
||||
DESCRIBE test;
|
||||
|
||||
+--------+----------------------+-----+------+---------+---------------+
|
||||
| Column | Type | Key | Null | Default | Semantic Type |
|
||||
+--------+----------------------+-----+------+---------+---------------+
|
||||
| id | Int32 | PRI | YES | | TAG |
|
||||
| i | String | | YES | | FIELD |
|
||||
| j | TimestampMillisecond | PRI | NO | | TIMESTAMP |
|
||||
| k | Boolean | | YES | | FIELD |
|
||||
+--------+----------------------+-----+------+---------+---------------+
|
||||
|
||||
ALTER TABLE test MODIFY I INTEGER;
|
||||
|
||||
Affected Rows: 0
|
||||
|
||||
SELECT * FROM test;
|
||||
|
||||
+----+---+-------------------------+-------+
|
||||
| id | i | j | k |
|
||||
+----+---+-------------------------+-------+
|
||||
| 1 | 1 | 1970-01-01T00:00:00.001 | false |
|
||||
| 2 | 2 | 1970-01-01T00:00:00.002 | true |
|
||||
| 3 | | 1970-01-01T00:00:00.003 | true |
|
||||
+----+---+-------------------------+-------+
|
||||
|
||||
DESCRIBE test;
|
||||
|
||||
+--------+----------------------+-----+------+---------+---------------+
|
||||
| Column | Type | Key | Null | Default | Semantic Type |
|
||||
+--------+----------------------+-----+------+---------+---------------+
|
||||
| id | Int32 | PRI | YES | | TAG |
|
||||
| i | Int32 | | YES | | FIELD |
|
||||
| j | TimestampMillisecond | PRI | NO | | TIMESTAMP |
|
||||
| k | Boolean | | YES | | FIELD |
|
||||
+--------+----------------------+-----+------+---------+---------------+
|
||||
|
||||
DROP TABLE test;
|
||||
|
||||
Affected Rows: 0
|
||||
|
||||
29
tests/cases/standalone/common/alter/change_col_type.sql
Normal file
29
tests/cases/standalone/common/alter/change_col_type.sql
Normal file
@@ -0,0 +1,29 @@
|
||||
CREATE TABLE test(id INTEGER PRIMARY KEY, i INTEGER NULL, j TIMESTAMP TIME INDEX, k BOOLEAN);
|
||||
|
||||
INSERT INTO test VALUES (1, 1, 1, false), (2, 2, 2, true);
|
||||
|
||||
ALTER TABLE test MODIFY "I" STRING;
|
||||
|
||||
ALTER TABLE test MODIFY k DATE;
|
||||
|
||||
ALTER TABLE test MODIFY id STRING;
|
||||
|
||||
ALTER TABLE test MODIFY j STRING;
|
||||
|
||||
ALTER TABLE test MODIFY I STRING;
|
||||
|
||||
SELECT * FROM test;
|
||||
|
||||
INSERT INTO test VALUES (3, "greptime", 3, true);
|
||||
|
||||
SELECT * FROM test;
|
||||
|
||||
DESCRIBE test;
|
||||
|
||||
ALTER TABLE test MODIFY I INTEGER;
|
||||
|
||||
SELECT * FROM test;
|
||||
|
||||
DESCRIBE test;
|
||||
|
||||
DROP TABLE test;
|
||||
@@ -0,0 +1,43 @@
|
||||
CREATE TABLE test(i TIMESTAMP TIME INDEX, j INTEGER NOT NULL);
|
||||
|
||||
Affected Rows: 0
|
||||
|
||||
INSERT INTO test VALUES (1, 1), (2, 2);
|
||||
|
||||
Affected Rows: 2
|
||||
|
||||
SELECT * FROM test;
|
||||
|
||||
+-------------------------+---+
|
||||
| i | j |
|
||||
+-------------------------+---+
|
||||
| 1970-01-01T00:00:00.001 | 1 |
|
||||
| 1970-01-01T00:00:00.002 | 2 |
|
||||
+-------------------------+---+
|
||||
|
||||
ALTER TABLE test MODIFY j STRING;
|
||||
|
||||
Error: 1004(InvalidArguments), Invalid alter table(test) request: column 'j' must be nullable to ensure safe conversion.
|
||||
|
||||
SELECT * FROM test;
|
||||
|
||||
+-------------------------+---+
|
||||
| i | j |
|
||||
+-------------------------+---+
|
||||
| 1970-01-01T00:00:00.001 | 1 |
|
||||
| 1970-01-01T00:00:00.002 | 2 |
|
||||
+-------------------------+---+
|
||||
|
||||
DESCRIBE test;
|
||||
|
||||
+--------+----------------------+-----+------+---------+---------------+
|
||||
| Column | Type | Key | Null | Default | Semantic Type |
|
||||
+--------+----------------------+-----+------+---------+---------------+
|
||||
| i | TimestampMillisecond | PRI | NO | | TIMESTAMP |
|
||||
| j | Int32 | | NO | | FIELD |
|
||||
+--------+----------------------+-----+------+---------+---------------+
|
||||
|
||||
DROP TABLE test;
|
||||
|
||||
Affected Rows: 0
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE test(i TIMESTAMP TIME INDEX, j INTEGER NOT NULL);
|
||||
|
||||
INSERT INTO test VALUES (1, 1), (2, 2);
|
||||
|
||||
SELECT * FROM test;
|
||||
|
||||
ALTER TABLE test MODIFY j STRING;
|
||||
|
||||
SELECT * FROM test;
|
||||
|
||||
DESCRIBE test;
|
||||
|
||||
DROP TABLE test;
|
||||
Reference in New Issue
Block a user