diff --git a/src/common/grpc-expr/src/alter.rs b/src/common/grpc-expr/src/alter.rs index fcf4486b4628..ac49069412cd 100644 --- a/src/common/grpc-expr/src/alter.rs +++ b/src/common/grpc-expr/src/alter.rs @@ -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 { + 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::>>()?; + + 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) -> Result 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 { diff --git a/src/common/meta/src/ddl/alter_table/region_request.rs b/src/common/meta/src/ddl/alter_table/region_request.rs index 265466b69442..b4223b8ea05d 100644 --- a/src/common/meta/src/ddl/alter_table/region_request.rs +++ b/src/common/meta/src/ddl/alter_table/region_request.rs @@ -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, + }] + } + )) + ); + } } diff --git a/src/operator/src/expr_factory.rs b/src/operator/src/expr_factory.rs index f0305ed981a5..17782c3addac 100644 --- a/src/operator/src/expr_factory.rs +++ b/src/operator/src/expr_factory.rs @@ -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()); + } } diff --git a/src/sql/src/parsers/alter_parser.rs b/src/sql/src/parsers/alter_parser.rs index 687604e37022..cf3e1d0fb8ed 100644 --- a/src/sql/src/parsers/alter_parser.rs +++ b/src/sql/src/parsers/alter_parser.rs @@ -30,24 +30,24 @@ impl<'a> ParserContext<'a> { } fn parse_alter_table(&mut self) -> std::result::Result { - 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 = diff --git a/src/sql/src/statements/alter.rs b/src/sql/src/statements/alter.rs index a54ba2d41b74..41a0997ecdb3 100644 --- a/src/sql/src/statements/alter.rs +++ b/src/sql/src/statements/alter.rs @@ -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, }, + /// `MODIFY [target_type]` + ChangeColumnType { + column_name: Ident, + target_type: DataType, + }, /// `DROP COLUMN ` DropColumn { name: Ident }, /// `RENAME ` @@ -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()) diff --git a/src/store-api/src/region_request.rs b/src/store-api/src/region_request.rs index 58210e45c2b7..6c4aaa28f47c 100644 --- a/src/store-api/src/region_request.rs +++ b/src/store-api/src/region_request.rs @@ -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 for AlterKind { .collect::>>()?; AlterKind::AddColumns { columns } } + alter_request::Kind::ChangeColumnTypes(x) => { + let columns = x + .change_column_types + .into_iter() + .map(|x| x.into()) + .collect::>(); + 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 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, diff --git a/tests/cases/standalone/common/alter/change_col_type.result b/tests/cases/standalone/common/alter/change_col_type.result new file mode 100644 index 000000000000..3d9500105a6d --- /dev/null +++ b/tests/cases/standalone/common/alter/change_col_type.result @@ -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 + diff --git a/tests/cases/standalone/common/alter/change_col_type.sql b/tests/cases/standalone/common/alter/change_col_type.sql new file mode 100644 index 000000000000..1eb95c719cdc --- /dev/null +++ b/tests/cases/standalone/common/alter/change_col_type.sql @@ -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; diff --git a/tests/cases/standalone/common/alter/change_col_type_not_null.result b/tests/cases/standalone/common/alter/change_col_type_not_null.result new file mode 100644 index 000000000000..79f03c9cb023 --- /dev/null +++ b/tests/cases/standalone/common/alter/change_col_type_not_null.result @@ -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 + diff --git a/tests/cases/standalone/common/alter/change_col_type_not_null.sql b/tests/cases/standalone/common/alter/change_col_type_not_null.sql new file mode 100644 index 000000000000..c91ae44a2c14 --- /dev/null +++ b/tests/cases/standalone/common/alter/change_col_type_not_null.sql @@ -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;