From b6cef77a5c261588961f84aaf484b6d01a5642f5 Mon Sep 17 00:00:00 2001 From: Lin Yihai Date: Tue, 29 Jul 2025 14:41:02 +0800 Subject: [PATCH] feat: add `SET DEFAULT` syntax (#6421) * feat: add `SET DEFAULT` syntax Signed-off-by: Yihai Lin * test: add `CURRENT_TIMESTAMP()` as default value for `SET DEFAULT` syntax Signed-off-by: Yihai Lin * refactor: Make the error types more precise. Signed-off-by: Yihai Lin * chore: a minor error display enchancement for `SET DEFAULT` Signed-off-by: Yihai Lin * refactor: Using `MODIFY COLUMN` for `DROP/SET DEFUALT` Signed-off-by: Yihai Lin * chore: update `greptime-proto` Signed-off-by: Yihai Lin --------- Signed-off-by: Yihai Lin --- Cargo.lock | 8 +- Cargo.toml | 2 +- src/common/grpc-expr/Cargo.toml | 1 + src/common/grpc-expr/src/alter.rs | 55 ++++- src/common/grpc-expr/src/error.rs | 24 ++ .../alter_logical_tables/update_metadata.rs | 9 +- .../meta/src/ddl/alter_table/executor.rs | 3 +- .../src/ddl/alter_table/region_request.rs | 1 + src/common/sql/Cargo.toml | 1 + src/common/sql/src/convert.rs | 26 ++- src/common/sql/src/error.rs | 13 +- src/operator/src/expr_helper.rs | 17 +- src/operator/src/statement/ddl.rs | 3 +- src/sql/src/parsers/alter_parser.rs | 210 ++++++++++++++---- src/sql/src/statements/alter.rs | 51 ++++- src/store-api/Cargo.toml | 2 + src/store-api/src/metadata.rs | 45 +++- src/store-api/src/region_request.rs | 44 +++- src/table/Cargo.toml | 2 + src/table/src/error.rs | 8 + src/table/src/metadata.rs | 48 +++- src/table/src/requests.rs | 13 +- ...ter_table_alter_column_drop_default.result | 6 +- .../alter_table_alter_column_drop_default.sql | 6 +- ...lter_table_alter_column_set_default.result | 128 +++++++++++ .../alter_table_alter_column_set_default.sql | 35 +++ .../common/create/current_timestamp.result | 2 +- 27 files changed, 680 insertions(+), 83 deletions(-) create mode 100644 tests/cases/standalone/common/alter/alter_table_alter_column_set_default.result create mode 100644 tests/cases/standalone/common/alter/alter_table_alter_column_set_default.sql diff --git a/Cargo.lock b/Cargo.lock index 6c4e421fae..2f20de1284 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2458,6 +2458,7 @@ dependencies = [ "common-error", "common-macro", "common-query", + "common-sql", "common-time", "datatypes", "paste", @@ -2722,6 +2723,7 @@ dependencies = [ "datatypes", "hex", "jsonb", + "serde_json", "snafu 0.8.5", "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=0cf6c04490d59435ee965edd2078e8855bd8471e)", ] @@ -5226,7 +5228,7 @@ dependencies = [ [[package]] name = "greptime-proto" version = "0.1.0" -source = "git+https://github.com/GreptimeTeam/greptime-proto.git?rev=7fcaa3e413947a7a28d9af95812af26c1939ce78#7fcaa3e413947a7a28d9af95812af26c1939ce78" +source = "git+https://github.com/GreptimeTeam/greptime-proto.git?rev=3bb33593a781504e025e6315572bc5dfdc1dc497#3bb33593a781504e025e6315572bc5dfdc1dc497" dependencies = [ "prost 0.13.5", "serde", @@ -12152,6 +12154,7 @@ dependencies = [ "common-macro", "common-meta", "common-recordbatch", + "common-sql", "common-time", "common-wal", "datafusion-expr", @@ -12166,6 +12169,7 @@ dependencies = [ "serde", "serde_json", "snafu 0.8.5", + "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=0cf6c04490d59435ee965edd2078e8855bd8471e)", "strum 0.27.1", "tokio", ] @@ -12514,6 +12518,7 @@ dependencies = [ "common-macro", "common-query", "common-recordbatch", + "common-sql", "common-telemetry", "common-test-util", "common-time", @@ -12534,6 +12539,7 @@ dependencies = [ "serde", "serde_json", "snafu 0.8.5", + "sqlparser 0.54.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=0cf6c04490d59435ee965edd2078e8855bd8471e)", "store-api", "tokio", "tokio-util", diff --git a/Cargo.toml b/Cargo.toml index f814e6c61e..46e92835f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -140,7 +140,7 @@ etcd-client = "0.14" fst = "0.4.7" futures = "0.3" futures-util = "0.3" -greptime-proto = { git = "https://github.com/GreptimeTeam/greptime-proto.git", rev = "7fcaa3e413947a7a28d9af95812af26c1939ce78" } +greptime-proto = { git = "https://github.com/GreptimeTeam/greptime-proto.git", rev = "3bb33593a781504e025e6315572bc5dfdc1dc497" } hex = "0.4" http = "1" humantime = "2.1" diff --git a/src/common/grpc-expr/Cargo.toml b/src/common/grpc-expr/Cargo.toml index fbb82be149..a689de7c01 100644 --- a/src/common/grpc-expr/Cargo.toml +++ b/src/common/grpc-expr/Cargo.toml @@ -14,6 +14,7 @@ common-catalog.workspace = true common-error.workspace = true common-macro.workspace = true common-query.workspace = true +common-sql.workspace = true common-time.workspace = true datatypes.workspace = true prost.workspace = true diff --git a/src/common/grpc-expr/src/alter.rs b/src/common/grpc-expr/src/alter.rs index ba3791432d..ca0108d8f7 100644 --- a/src/common/grpc-expr/src/alter.rs +++ b/src/common/grpc-expr/src/alter.rs @@ -27,16 +27,17 @@ use common_query::AddColumnLocation; use datatypes::schema::{ColumnSchema, FulltextOptions, RawSchema, SkippingIndexOptions}; use snafu::{ensure, OptionExt, ResultExt}; use store_api::region_request::{SetRegionOption, UnsetRegionOption}; -use table::metadata::TableId; +use table::metadata::{TableId, TableMeta}; use table::requests::{ - AddColumnRequest, AlterKind, AlterTableRequest, ModifyColumnTypeRequest, SetIndexOption, - UnsetIndexOption, + AddColumnRequest, AlterKind, AlterTableRequest, ModifyColumnTypeRequest, SetDefaultRequest, + SetIndexOption, UnsetIndexOption, }; use crate::error::{ - InvalidColumnDefSnafu, InvalidIndexOptionSnafu, InvalidSetFulltextOptionRequestSnafu, - InvalidSetSkippingIndexOptionRequestSnafu, InvalidSetTableOptionRequestSnafu, - InvalidUnsetTableOptionRequestSnafu, MissingAlterIndexOptionSnafu, MissingFieldSnafu, + ColumnNotFoundSnafu, InvalidColumnDefSnafu, InvalidIndexOptionSnafu, + InvalidSetFulltextOptionRequestSnafu, InvalidSetSkippingIndexOptionRequestSnafu, + InvalidSetTableOptionRequestSnafu, InvalidUnsetTableOptionRequestSnafu, + MissingAlterIndexOptionSnafu, MissingFieldSnafu, MissingTableMetaSnafu, MissingTimestampColumnSnafu, Result, UnknownLocationTypeSnafu, }; @@ -97,7 +98,13 @@ fn unset_index_option_from_proto(unset_index: api::v1::UnsetIndex) -> Result Result { +/// +/// note: `table_meta` must not be None if [`AlterTableExpr`] is `SetDefault` +pub fn alter_expr_to_request( + table_id: TableId, + expr: AlterTableExpr, + table_meta: Option<&TableMeta>, +) -> Result { let catalog_name = expr.catalog_name; let schema_name = expr.schema_name; let kind = expr.kind.context(MissingFieldSnafu { field: "kind" })?; @@ -218,6 +225,32 @@ pub fn alter_expr_to_request(table_id: TableId, expr: AlterTableExpr) -> Result< .collect::>>()?; AlterKind::DropDefaults { names } } + Kind::SetDefaults(o) => { + let table_meta = table_meta.context(MissingTableMetaSnafu { table_id })?; + let defaults = o + .set_defaults + .into_iter() + .map(|col| { + let column_scheme = table_meta + .schema + .column_schema_by_name(&col.column_name) + .context(ColumnNotFoundSnafu { + column_name: &col.column_name, + })?; + let default_constraint = common_sql::convert::deserialize_default_constraint( + col.default_constraint.as_slice(), + &col.column_name, + &column_scheme.data_type, + ) + .context(crate::error::SqlCommonSnafu)?; + Ok(SetDefaultRequest { + column_name: col.column_name, + default_constraint, + }) + }) + .collect::>>()?; + AlterKind::SetDefaults { defaults } + } }; let request = AlterTableRequest { @@ -317,7 +350,7 @@ mod tests { })), }; - let alter_request = alter_expr_to_request(1, expr).unwrap(); + let alter_request = alter_expr_to_request(1, expr, None).unwrap(); assert_eq!(alter_request.catalog_name, ""); assert_eq!(alter_request.schema_name, ""); assert_eq!("monitor".to_string(), alter_request.table_name); @@ -381,7 +414,7 @@ mod tests { })), }; - let alter_request = alter_expr_to_request(1, expr).unwrap(); + let alter_request = alter_expr_to_request(1, expr, None).unwrap(); assert_eq!(alter_request.catalog_name, ""); assert_eq!(alter_request.schema_name, ""); assert_eq!("monitor".to_string(), alter_request.table_name); @@ -433,7 +466,7 @@ mod tests { })), }; - let alter_request = alter_expr_to_request(1, expr).unwrap(); + let alter_request = alter_expr_to_request(1, expr, None).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); @@ -465,7 +498,7 @@ mod tests { })), }; - let alter_request = alter_expr_to_request(1, expr).unwrap(); + let alter_request = alter_expr_to_request(1, expr, None).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); diff --git a/src/common/grpc-expr/src/error.rs b/src/common/grpc-expr/src/error.rs index cd3fe5af30..e4f441d882 100644 --- a/src/common/grpc-expr/src/error.rs +++ b/src/common/grpc-expr/src/error.rs @@ -161,6 +161,27 @@ pub enum Error { #[snafu(source)] error: datatypes::error::Error, }, + + #[snafu(display("Sql common error"))] + SqlCommon { + source: common_sql::error::Error, + #[snafu(implicit)] + location: Location, + }, + + #[snafu(display("Missing required field in protobuf, column name: {}", column_name))] + ColumnNotFound { + column_name: String, + #[snafu(implicit)] + location: Location, + }, + + #[snafu(display("Need table metadata, but not found, table_id: {}", table_id))] + MissingTableMeta { + table_id: u32, + #[snafu(implicit)] + location: Location, + }, } pub type Result = std::result::Result; @@ -190,6 +211,9 @@ impl ErrorExt for Error { | Error::InvalidSetSkippingIndexOptionRequest { .. } | Error::MissingAlterIndexOption { .. } | Error::InvalidIndexOption { .. } => StatusCode::InvalidArguments, + Error::ColumnNotFound { .. } => StatusCode::TableColumnNotFound, + Error::SqlCommon { source, .. } => source.status_code(), + Error::MissingTableMeta { .. } => StatusCode::Unexpected, } } diff --git a/src/common/meta/src/ddl/alter_logical_tables/update_metadata.rs b/src/common/meta/src/ddl/alter_logical_tables/update_metadata.rs index f730f0cda6..50e0f9c35f 100644 --- a/src/common/meta/src/ddl/alter_logical_tables/update_metadata.rs +++ b/src/common/meta/src/ddl/alter_logical_tables/update_metadata.rs @@ -94,9 +94,12 @@ impl AlterLogicalTablesProcedure { let table_info = TableInfo::try_from(table.table_info.clone()) .context(error::ConvertRawTableInfoSnafu)?; let table_ref = task.table_ref(); - let request = - alter_expr_to_request(table.table_info.ident.table_id, task.alter_table.clone()) - .context(ConvertAlterTableRequestSnafu)?; + let request = alter_expr_to_request( + table.table_info.ident.table_id, + task.alter_table.clone(), + Some(&table_info.meta), + ) + .context(ConvertAlterTableRequestSnafu)?; let new_meta = table_info .meta .builder_with_alter_kind(table_ref.table, &request.alter_kind) diff --git a/src/common/meta/src/ddl/alter_table/executor.rs b/src/common/meta/src/ddl/alter_table/executor.rs index 0bacade17e..fda5839965 100644 --- a/src/common/meta/src/ddl/alter_table/executor.rs +++ b/src/common/meta/src/ddl/alter_table/executor.rs @@ -270,7 +270,7 @@ fn build_new_table_info( let catalog_name = &table_info.catalog_name; let table_name = &table_info.name; let table_id = table_info.ident.table_id; - let request = alter_expr_to_request(table_id, alter_table_expr) + let request = alter_expr_to_request(table_id, alter_table_expr, Some(&table_info.meta)) .context(error::ConvertAlterTableRequestSnafu)?; let new_meta = table_info @@ -302,6 +302,7 @@ fn build_new_table_info( | AlterKind::SetIndexes { .. } | AlterKind::UnsetIndexes { .. } | AlterKind::DropDefaults { .. } => {} + AlterKind::SetDefaults { .. } => {} } info!( 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 dab63cc85a..950dd92dce 100644 --- a/src/common/meta/src/ddl/alter_table/region_request.rs +++ b/src/common/meta/src/ddl/alter_table/region_request.rs @@ -111,6 +111,7 @@ fn create_proto_alter_kind( Kind::SetIndexes(v) => Ok(Some(alter_request::Kind::SetIndexes(v.clone()))), Kind::UnsetIndexes(v) => Ok(Some(alter_request::Kind::UnsetIndexes(v.clone()))), Kind::DropDefaults(v) => Ok(Some(alter_request::Kind::DropDefaults(v.clone()))), + Kind::SetDefaults(v) => Ok(Some(alter_request::Kind::SetDefaults(v.clone()))), } } diff --git a/src/common/sql/Cargo.toml b/src/common/sql/Cargo.toml index e9596b6459..daac4c3f56 100644 --- a/src/common/sql/Cargo.toml +++ b/src/common/sql/Cargo.toml @@ -15,6 +15,7 @@ datafusion-sql.workspace = true datatypes.workspace = true hex = "0.4" jsonb.workspace = true +serde_json.workspace = true snafu.workspace = true sqlparser.workspace = true diff --git a/src/common/sql/src/convert.rs b/src/common/sql/src/convert.rs index 7f625ee887..fb9282a70c 100644 --- a/src/common/sql/src/convert.rs +++ b/src/common/sql/src/convert.rs @@ -17,6 +17,7 @@ use std::str::FromStr; use common_time::timezone::Timezone; use common_time::Timestamp; use datatypes::prelude::ConcreteDataType; +use datatypes::schema::ColumnDefaultConstraint; use datatypes::types::{parse_string_to_json_type_value, parse_string_to_vector_type_value}; use datatypes::value::{OrderedF32, OrderedF64, Value}; use snafu::{ensure, OptionExt, ResultExt}; @@ -29,8 +30,8 @@ pub use sqlparser::ast::{ use crate::error::{ ColumnTypeMismatchSnafu, ConvertSqlValueSnafu, ConvertStrSnafu, DatatypeSnafu, - InvalidCastSnafu, InvalidSqlValueSnafu, InvalidUnaryOpSnafu, ParseSqlValueSnafu, Result, - TimestampOverflowSnafu, UnsupportedUnaryOpSnafu, + DeserializeSnafu, InvalidCastSnafu, InvalidSqlValueSnafu, InvalidUnaryOpSnafu, + ParseSqlValueSnafu, Result, TimestampOverflowSnafu, UnsupportedUnaryOpSnafu, }; fn parse_sql_number(n: &str) -> Result @@ -368,6 +369,27 @@ pub(crate) fn parse_hex_string(s: &str) -> Result { } } +/// Deserialize default constraint from json bytes +pub fn deserialize_default_constraint( + bytes: &[u8], + column_name: &str, + data_type: &ConcreteDataType, +) -> Result> { + let json = String::from_utf8_lossy(bytes); + let default_constraint = serde_json::from_str(&json).context(DeserializeSnafu { json })?; + let column_def = sqlparser::ast::ColumnOptionDef { + name: None, + option: sqlparser::ast::ColumnOption::Default(default_constraint), + }; + + crate::default_constraint::parse_column_default_constraint( + column_name, + data_type, + &[column_def], + None, + ) +} + #[cfg(test)] mod test { use common_base::bytes::Bytes; diff --git a/src/common/sql/src/error.rs b/src/common/sql/src/error.rs index 2b2ab8a270..8ef046a8c5 100644 --- a/src/common/sql/src/error.rs +++ b/src/common/sql/src/error.rs @@ -55,7 +55,7 @@ pub enum Error { }, #[snafu(display( - "Unsupported expr in default constraint: {:?} for column: {}", + "Unsupported expr in default constraint: {} for column: {}", expr, column_name ))] @@ -131,6 +131,15 @@ pub enum Error { #[snafu(implicit)] location: Location, }, + + #[snafu(display("Failed to deserialize data, json: {}", json))] + Deserialize { + #[snafu(source)] + error: serde_json::Error, + #[snafu(implicit)] + location: Location, + json: String, + }, } impl ErrorExt for Error { @@ -147,6 +156,8 @@ impl ErrorExt for Error { | InvalidCast { .. } | ConvertStr { .. } | TimestampOverflow { .. } => StatusCode::InvalidArguments, + Deserialize { .. } => StatusCode::Unexpected, + Datatype { source, .. } => source.status_code(), ConvertSqlValue { .. } => StatusCode::Unsupported, } diff --git a/src/operator/src/expr_helper.rs b/src/operator/src/expr_helper.rs index 9220388e6d..c992b322f9 100644 --- a/src/operator/src/expr_helper.rs +++ b/src/operator/src/expr_helper.rs @@ -26,7 +26,7 @@ use api::v1::{ ColumnDataType, ColumnDataTypeExtension, CreateFlowExpr, CreateTableExpr, CreateViewExpr, DropColumn, DropColumns, DropDefaults, ExpireAfter, FulltextBackend as PbFulltextBackend, ModifyColumnType, ModifyColumnTypes, RenameTable, SemanticType, SetDatabaseOptions, - SetFulltext, SetIndex, SetIndexes, SetInverted, SetSkipping, SetTableOptions, + SetDefaults, SetFulltext, SetIndex, SetIndexes, SetInverted, SetSkipping, SetTableOptions, SkippingIndexType as PbSkippingIndexType, TableName, UnsetDatabaseOptions, UnsetFulltext, UnsetIndex, UnsetIndexes, UnsetInverted, UnsetSkipping, UnsetTableOptions, }; @@ -664,6 +664,21 @@ pub(crate) fn to_alter_table_expr( .collect::>>()?, }) } + AlterTableOperation::SetDefaults { defaults } => AlterTableKind::SetDefaults(SetDefaults { + set_defaults: defaults + .into_iter() + .map(|col| { + let column_name = col.column_name.to_string(); + let default_constraint = serde_json::to_string(&col.default_constraint) + .context(EncodeJsonSnafu)? + .into_bytes(); + Ok(api::v1::SetDefault { + column_name, + default_constraint, + }) + }) + .collect::>>()?, + }), }; Ok(AlterTableExpr { diff --git a/src/operator/src/statement/ddl.rs b/src/operator/src/statement/ddl.rs index 2db857ae69..707c8b90ae 100644 --- a/src/operator/src/statement/ddl.rs +++ b/src/operator/src/statement/ddl.rs @@ -1591,7 +1591,8 @@ pub fn verify_alter( expr: AlterTableExpr, ) -> Result { let request: AlterTableRequest = - common_grpc_expr::alter_expr_to_request(table_id, expr).context(AlterExprToRequestSnafu)?; + common_grpc_expr::alter_expr_to_request(table_id, expr, Some(&table_info.meta)) + .context(AlterExprToRequestSnafu)?; let AlterTableRequest { table_name, diff --git a/src/sql/src/parsers/alter_parser.rs b/src/sql/src/parsers/alter_parser.rs index 9c3a48d7b2..ce2df198ab 100644 --- a/src/sql/src/parsers/alter_parser.rs +++ b/src/sql/src/parsers/alter_parser.rs @@ -30,7 +30,8 @@ use crate::parsers::utils::{ }; use crate::statements::alter::{ AddColumn, AlterDatabase, AlterDatabaseOperation, AlterTable, AlterTableOperation, - DropDefaultsOperation, KeyValueOption, SetIndexOperation, UnsetIndexOperation, + DropDefaultsOperation, KeyValueOption, SetDefaultsOperation, SetIndexOperation, + UnsetIndexOperation, }; use crate::statements::statement::Statement; use crate::util::parse_option_string; @@ -156,7 +157,6 @@ impl ParserContext<'_> { .collect(); AlterTableOperation::SetTableOptions { options } } - Keyword::ALTER => self.parse_alter_columns()?, _ => self.expected( "ADD or DROP or MODIFY or RENAME or SET after ALTER TABLE", self.parser.peek_token(), @@ -169,29 +169,6 @@ impl ParserContext<'_> { Ok(AlterTable::new(table_name, alter_operation)) } - // Parse the following: ALTER TABLE table_name ALTER ... - fn parse_alter_columns(&mut self) -> Result { - let _ = self.parser.next_token(); - let _ = self.parser.next_token(); - let ts = self.parser.next_token(); - match ts.token { - // Parse `DROP DEFAULT`: ALTER TABLE `table_name` ALTER `a` DROP DEFAULT, ALTER `b` DROP DEFAULT, ... - Token::Word(w) if w.keyword == Keyword::DROP => { - let ts = self.parser.peek_token(); - match ts.token { - Token::Word(w) if w.keyword == Keyword::DEFAULT => { - self.parser.prev_token(); - self.parser.prev_token(); - self.parser.prev_token(); - self.parse_alter_table_drop_default() - } - _ => self.expected("DEFAULT is expecting after DROP", ts), - } - } - _ => self.expected("DROP after ALTER COLUMN", ts), - } - } - fn parse_alter_table_unset(&mut self) -> Result { let _ = self.parser.next_token(); let keys = self @@ -222,12 +199,43 @@ impl ParserContext<'_> { } } - fn parse_alter_table_drop_default(&mut self) -> Result { - let columns = self - .parser - .parse_comma_separated(parse_alter_column_drop_default) - .context(error::SyntaxSnafu)?; - Ok(AlterTableOperation::DropDefaults { columns }) + fn parse_alter_table_drop_default( + &mut self, + column_name: Ident, + ) -> Result { + let drop_default = DropDefaultsOperation(column_name); + if self.parser.consume_token(&Token::Comma) { + let mut columns = self + .parser + .parse_comma_separated(parse_alter_column_drop_default) + .context(error::SyntaxSnafu)?; + columns.insert(0, drop_default); + Ok(AlterTableOperation::DropDefaults { columns }) + } else { + Ok(AlterTableOperation::DropDefaults { + columns: vec![drop_default], + }) + } + } + + fn parse_alter_table_set_default(&mut self, column_name: Ident) -> Result { + let default_constraint = self.parser.parse_expr().context(error::SyntaxSnafu)?; + let set_default = SetDefaultsOperation { + column_name, + default_constraint, + }; + if self.parser.consume_token(&Token::Comma) { + let mut defaults = self + .parser + .parse_comma_separated(parse_alter_column_set_default) + .context(error::SyntaxSnafu)?; + defaults.insert(0, set_default); + Ok(AlterTableOperation::SetDefaults { defaults }) + } else { + Ok(AlterTableOperation::SetDefaults { + defaults: vec![set_default], + }) + } } fn parse_alter_table_modify(&mut self) -> Result { @@ -248,7 +256,23 @@ impl ParserContext<'_> { } else if w.keyword == Keyword::SET { // consume the current token. self.parser.next_token(); - self.parse_alter_column_set_index(column_name) + if let Token::Word(w) = self.parser.peek_token().token + && matches!(w.keyword, Keyword::DEFAULT) + { + self.parser + .expect_keyword(Keyword::DEFAULT) + .context(error::SyntaxSnafu)?; + self.parse_alter_table_set_default(column_name) + } else { + self.parse_alter_column_set_index(column_name) + } + } else if w.keyword == Keyword::DROP { + // consume the current token. + self.parser.next_token(); + self.parser + .expect_keyword(Keyword::DEFAULT) + .context(error::SyntaxSnafu)?; + self.parse_alter_table_drop_default(column_name) } else { let data_type = self.parser.parse_data_type().context(error::SyntaxSnafu)?; Ok(AlterTableOperation::ModifyColumnType { @@ -348,9 +372,9 @@ impl ParserContext<'_> { .context(error::SyntaxSnafu)?; self.parse_alter_column_skipping(column_name) } - _ => self.expected( + t => self.expected( format!("{:?} OR INVERTED OR SKIPPING INDEX", Keyword::FULLTEXT).as_str(), - self.parser.peek_token(), + t, ), } } @@ -420,17 +444,44 @@ impl ParserContext<'_> { fn parse_alter_column_drop_default( parser: &mut Parser, ) -> std::result::Result { - parser.next_token(); + parser.expect_keywords(&[Keyword::MODIFY, Keyword::COLUMN])?; let column_name = ParserContext::canonicalize_identifier(parser.parse_identifier()?); - if parser.parse_keywords(&[Keyword::DROP, Keyword::DEFAULT]) { - Ok(DropDefaultsOperation(column_name)) - } else { - let not_drop = parser.peek_token(); - parser.next_token(); - let not_default = parser.peek_token(); - Err(ParserError::ParserError(format!( - "Unexpected keyword, expect DROP DEFAULT, got: `{not_drop} {not_default}`" - ))) + let t = parser.next_token(); + match t.token { + Token::Word(w) if w.keyword == Keyword::DROP => { + parser.expect_keyword(Keyword::DEFAULT)?; + Ok(DropDefaultsOperation(column_name)) + } + _ => Err(ParserError::ParserError(format!( + "Unexpected keyword, expect DROP, got: `{t}`" + ))), + } +} + +fn parse_alter_column_set_default( + parser: &mut Parser, +) -> std::result::Result { + parser.expect_keywords(&[Keyword::MODIFY, Keyword::COLUMN])?; + let column_name = ParserContext::canonicalize_identifier(parser.parse_identifier()?); + let t = parser.next_token(); + match t.token { + Token::Word(w) if w.keyword == Keyword::SET => { + parser.expect_keyword(Keyword::DEFAULT)?; + if let Ok(default_constraint) = parser.parse_expr() { + Ok(SetDefaultsOperation { + column_name, + default_constraint, + }) + } else { + Err(ParserError::ParserError(format!( + "Invalid default value after SET DEFAULT, got: `{}`", + parser.peek_token() + ))) + } + } + _ => Err(ParserError::ParserError(format!( + "Unexpected keyword, expect SET, got: `{t}`" + ))), } } @@ -1183,7 +1234,7 @@ mod tests { for col in columns { let sql = col .iter() - .map(|x| format!("ALTER {x} DROP DEFAULT")) + .map(|x| format!("MODIFY COLUMN {x} DROP DEFAULT")) .collect::>() .join(","); let sql = format!("ALTER TABLE test_table {sql}"); @@ -1214,4 +1265,73 @@ mod tests { } } } + + #[test] + fn test_parse_alter_set_default() { + let columns = vec![vec!["a"], vec!["a", "b"], vec!["a", "b", "c"]]; + for col in columns { + let sql = col + .iter() + .map(|x| format!("MODIFY COLUMN {x} SET DEFAULT 100")) + .collect::>() + .join(","); + let sql = format!("ALTER TABLE test_table {sql}"); + 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 { .. }); + match statement { + Statement::AlterTable(alter_table) => { + assert_eq!("test_table", alter_table.table_name().0[0].value); + let alter_operation = alter_table.alter_operation(); + match alter_operation { + AlterTableOperation::SetDefaults { defaults } => { + assert_eq!(col.len(), defaults.len()); + for i in 0..defaults.len() { + assert_eq!(col[i], defaults[i].column_name.to_string()); + assert_eq!( + "100".to_string(), + defaults[i].default_constraint.to_string() + ); + } + } + _ => unreachable!(), + } + } + _ => unreachable!(), + } + } + } + + #[test] + fn test_parse_alter_set_default_invalid() { + let sql = "ALTER TABLE test_table MODIFY COLUMN a SET 100;"; + let result = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap_err(); + let err = result.output_msg(); + assert_eq!(err, "Invalid SQL syntax: sql parser error: Expected FULLTEXT OR INVERTED OR SKIPPING INDEX, found: 100"); + + let sql = "ALTER TABLE test_table MODIFY COLUMN a SET DEFAULT 100, b SET DEFAULT 200"; + let result = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap_err(); + let err = result.output_msg(); + assert_eq!(err, "Invalid SQL syntax: sql parser error: Expected: MODIFY, found: b at Line: 1, Column: 57"); + + let sql = "ALTER TABLE test_table MODIFY COLUMN a SET DEFAULT 100, MODIFY COLUMN b DROP DEFAULT 200"; + let result = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap_err(); + let err = result.output_msg(); + assert_eq!( + err, + "Invalid SQL syntax: sql parser error: Unexpected keyword, expect SET, got: `DROP`" + ); + } } diff --git a/src/sql/src/statements/alter.rs b/src/sql/src/statements/alter.rs index ae865c5b7b..63d2bb2b9f 100644 --- a/src/sql/src/statements/alter.rs +++ b/src/sql/src/statements/alter.rs @@ -19,7 +19,7 @@ use common_query::AddColumnLocation; use datatypes::schema::{FulltextOptions, SkippingIndexOptions}; use itertools::Itertools; use serde::Serialize; -use sqlparser::ast::{ColumnDef, DataType, Ident, ObjectName, TableConstraint}; +use sqlparser::ast::{ColumnDef, DataType, Expr, Ident, ObjectName, TableConstraint}; use sqlparser_derive::{Visit, VisitMut}; #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut, Serialize)] @@ -95,12 +95,22 @@ pub enum AlterTableOperation { DropDefaults { columns: Vec, }, + /// `ALTER SET DEFAULT ` + SetDefaults { + defaults: Vec, + }, } #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut, Serialize)] /// `ALTER DROP DEFAULT` pub struct DropDefaultsOperation(pub Ident); +#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut, Serialize)] +pub struct SetDefaultsOperation { + pub column_name: Ident, + pub default_constraint: Expr, +} + #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut, Serialize)] pub enum SetIndexOperation { /// `MODIFY COLUMN SET FULLTEXT INDEX [WITH ]` @@ -214,10 +224,22 @@ impl Display for AlterTableOperation { AlterTableOperation::DropDefaults { columns } => { let columns = columns .iter() - .map(|column| format!("ALTER {} DROP DEFAULT", column.0)) + .map(|column| format!("MODIFY COLUMN {} DROP DEFAULT", column.0)) .join(", "); write!(f, "{columns}") } + AlterTableOperation::SetDefaults { defaults } => { + let defaults = defaults + .iter() + .map(|column| { + format!( + "MODIFY COLUMN {} SET DEFAULT {}", + column.column_name, column.default_constraint + ) + }) + .join(", "); + write!(f, "{defaults}") + } } } } @@ -502,7 +524,7 @@ ALTER TABLE monitor MODIFY COLUMN a SET INVERTED INDEX"#, } } - let sql = "ALTER TABLE monitor ALTER a DROP DEFAULT"; + let sql = "ALTER TABLE monitor MODIFY COLUMN a DROP DEFAULT"; let stmts = ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) .unwrap(); @@ -514,7 +536,28 @@ ALTER TABLE monitor MODIFY COLUMN a SET INVERTED INDEX"#, let new_sql = format!("\n{}", set); assert_eq!( r#" -ALTER TABLE monitor ALTER a DROP DEFAULT"#, +ALTER TABLE monitor MODIFY COLUMN a DROP DEFAULT"#, + &new_sql + ); + } + _ => { + unreachable!(); + } + } + + let sql = "ALTER TABLE monitor MODIFY COLUMN a SET DEFAULT 'default_for_a'"; + let stmts = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, stmts.len()); + assert_matches!(&stmts[0], Statement::AlterTable { .. }); + + match &stmts[0] { + Statement::AlterTable(set) => { + let new_sql = format!("\n{}", set); + assert_eq!( + r#" +ALTER TABLE monitor MODIFY COLUMN a SET DEFAULT 'default_for_a'"#, &new_sql ); } diff --git a/src/store-api/Cargo.toml b/src/store-api/Cargo.toml index 4cc2cf1232..c6fce64e8c 100644 --- a/src/store-api/Cargo.toml +++ b/src/store-api/Cargo.toml @@ -16,6 +16,7 @@ common-error.workspace = true common-grpc.workspace = true common-macro.workspace = true common-recordbatch.workspace = true +common-sql.workspace = true common-time.workspace = true common-wal.workspace = true datafusion-expr.workspace = true @@ -30,6 +31,7 @@ prost.workspace = true serde.workspace = true serde_json.workspace = true snafu.workspace = true +sqlparser.workspace = true strum.workspace = true tokio.workspace = true diff --git a/src/store-api/src/metadata.rs b/src/store-api/src/metadata.rs index eb04378a15..9c426169fa 100644 --- a/src/store-api/src/metadata.rs +++ b/src/store-api/src/metadata.rs @@ -592,6 +592,7 @@ impl RegionMetadataBuilder { AlterKind::DropDefaults { names } => { self.drop_defaults(names)?; } + AlterKind::SetDefaults { columns } => self.set_defaults(&columns)?, } Ok(self) } @@ -872,6 +873,38 @@ impl RegionMetadataBuilder { } Ok(()) } + + fn set_defaults(&mut self, set_defaults: &[crate::region_request::SetDefault]) -> Result<()> { + for set_default in set_defaults.iter() { + let meta = self + .column_metadatas + .iter_mut() + .find(|col| col.column_schema.name == set_default.name); + if let Some(meta) = meta { + let default_constraint = common_sql::convert::deserialize_default_constraint( + set_default.default_constraint.as_slice(), + &meta.column_schema.name, + &meta.column_schema.data_type, + ) + .context(SqlCommonSnafu)?; + + meta.column_schema = meta + .column_schema + .clone() + .with_default_constraint(default_constraint) + .with_context(|_| CastDefaultValueSnafu { + reason: format!("Failed to set default : {set_default:?}"), + })?; + } else { + return InvalidRegionRequestSnafu { + region_id: self.region_id, + err: format!("column {} not found", set_default.name), + } + .fail(); + } + } + Ok(()) + } } /// Fields skipped in serialization. @@ -1089,11 +1122,21 @@ pub enum MetadataError { #[snafu(source)] error: datatypes::error::Error, }, + + #[snafu(display("Sql common error"))] + SqlCommon { + source: common_sql::error::Error, + #[snafu(implicit)] + location: Location, + }, } impl ErrorExt for MetadataError { fn status_code(&self) -> StatusCode { - StatusCode::InvalidArguments + match self { + Self::SqlCommon { source, .. } => source.status_code(), + _ => StatusCode::InvalidArguments, + } } fn as_any(&self) -> &dyn Any { diff --git a/src/store-api/src/region_request.rs b/src/store-api/src/region_request.rs index fbfc5a7fbc..74e9dac029 100644 --- a/src/store-api/src/region_request.rs +++ b/src/store-api/src/region_request.rs @@ -562,6 +562,16 @@ pub enum AlterKind { /// Name of columns to drop. names: Vec, }, + /// Set column default value. + SetDefaults { + /// Columns to change. + columns: Vec, + }, +} +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct SetDefault { + pub name: String, + pub default_constraint: Vec, } #[derive(Debug, PartialEq, Eq, Clone)] @@ -738,7 +748,12 @@ impl AlterKind { AlterKind::DropDefaults { names } => { names .iter() - .try_for_each(|name| Self::validate_column_to_drop(name, metadata))?; + .try_for_each(|name| Self::validate_column_existence(name, metadata))?; + } + AlterKind::SetDefaults { columns } => { + columns + .iter() + .try_for_each(|col| Self::validate_column_existence(&col.name, metadata))?; } } Ok(()) @@ -772,6 +787,9 @@ impl AlterKind { AlterKind::DropDefaults { names } => names .iter() .any(|name| metadata.column_by_name(name).is_some()), + AlterKind::SetDefaults { columns } => columns + .iter() + .any(|x| metadata.column_by_name(&x.name).is_some()), } } @@ -818,6 +836,18 @@ impl AlterKind { Ok(()) } + + /// Returns an error if the column isn't exist. + fn validate_column_existence(column_name: &String, metadata: &RegionMetadata) -> Result<()> { + metadata + .column_by_name(column_name) + .context(InvalidRegionRequestSnafu { + region_id: metadata.region_id, + err: format!("column {} not found", column_name), + })?; + + Ok(()) + } } impl TryFrom for AlterKind { @@ -882,6 +912,18 @@ impl TryFrom for AlterKind { alter_request::Kind::DropDefaults(x) => AlterKind::DropDefaults { names: x.drop_defaults.into_iter().map(|x| x.column_name).collect(), }, + alter_request::Kind::SetDefaults(x) => AlterKind::SetDefaults { + columns: x + .set_defaults + .into_iter() + .map(|x| { + Ok(SetDefault { + name: x.column_name, + default_constraint: x.default_constraint.clone(), + }) + }) + .collect::>>()?, + }, }; Ok(alter_kind) diff --git a/src/table/Cargo.toml b/src/table/Cargo.toml index a220f587f7..0b330dc5f3 100644 --- a/src/table/Cargo.toml +++ b/src/table/Cargo.toml @@ -21,6 +21,7 @@ common-error.workspace = true common-macro.workspace = true common-query.workspace = true common-recordbatch.workspace = true +common-sql.workspace = true common-telemetry.workspace = true common-time.workspace = true datafusion.workspace = true @@ -38,6 +39,7 @@ once_cell.workspace = true paste.workspace = true serde.workspace = true snafu.workspace = true +sqlparser.workspace = true store-api.workspace = true tokio.workspace = true diff --git a/src/table/src/error.rs b/src/table/src/error.rs index 192beea1cd..431b5a4db4 100644 --- a/src/table/src/error.rs +++ b/src/table/src/error.rs @@ -195,6 +195,13 @@ pub enum Error { #[snafu(implicit)] location: Location, }, + + #[snafu(display("Sql common error"))] + SqlCommon { + source: common_sql::error::Error, + #[snafu(implicit)] + location: Location, + }, } impl ErrorExt for Error { @@ -211,6 +218,7 @@ impl ErrorExt for Error { Error::CastDefaultValue { source, .. } => source.status_code(), Error::TablesRecordBatch { .. } => StatusCode::Unexpected, Error::ColumnExists { .. } => StatusCode::TableColumnExists, + Error::SqlCommon { source, .. } => source.status_code(), Error::SchemaBuild { source, .. } | Error::SetFulltextOptions { source, .. } => { source.status_code() } diff --git a/src/table/src/metadata.rs b/src/table/src/metadata.rs index 1d91164fee..7e2bbe49fe 100644 --- a/src/table/src/metadata.rs +++ b/src/table/src/metadata.rs @@ -35,8 +35,8 @@ use store_api::storage::{ColumnDescriptor, ColumnDescriptorBuilder, ColumnId, Re use crate::error::{self, Result}; use crate::requests::{ - AddColumnRequest, AlterKind, ModifyColumnTypeRequest, SetIndexOption, TableOptions, - UnsetIndexOption, + AddColumnRequest, AlterKind, ModifyColumnTypeRequest, SetDefaultRequest, SetIndexOption, + TableOptions, UnsetIndexOption, }; use crate::table_reference::TableReference; @@ -246,6 +246,7 @@ impl TableMeta { AlterKind::SetIndexes { options } => self.set_indexes(table_name, options), AlterKind::UnsetIndexes { options } => self.unset_indexes(table_name, options), AlterKind::DropDefaults { names } => self.drop_defaults(table_name, names), + AlterKind::SetDefaults { defaults } => self.set_defaults(table_name, defaults), } } @@ -991,6 +992,49 @@ impl TableMeta { Ok(meta_builder) } + + fn set_defaults( + &self, + table_name: &str, + set_defaults: &[SetDefaultRequest], + ) -> Result { + let table_schema = &self.schema; + let mut meta_builder = self.new_meta_builder(); + let mut columns = Vec::with_capacity(table_schema.num_columns()); + for column_schema in table_schema.column_schemas() { + if let Some(set_default) = set_defaults + .iter() + .find(|s| s.column_name == column_schema.name) + { + let new_column_schema = column_schema.clone(); + let new_column_schema = new_column_schema + .with_default_constraint(set_default.default_constraint.clone()) + .with_context(|_| error::SchemaBuildSnafu { + msg: format!("Table {table_name} cannot set default values"), + })?; + columns.push(new_column_schema); + } else { + columns.push(column_schema.clone()); + } + } + + let mut builder = SchemaBuilder::try_from_columns(columns) + .with_context(|_| error::SchemaBuildSnafu { + msg: format!("Failed to convert column schemas into schema for table {table_name}"), + })? + // Also bump the schema version. + .version(table_schema.version() + 1); + for (k, v) in table_schema.metadata().iter() { + builder = builder.add_metadata(k, v); + } + let new_schema = builder.build().with_context(|_| error::SchemaBuildSnafu { + msg: format!("Table {table_name} cannot set default values"), + })?; + + let _ = meta_builder.schema(Arc::new(new_schema)); + + Ok(meta_builder) + } } #[derive(Clone, Debug, PartialEq, Eq, Builder)] diff --git a/src/table/src/requests.rs b/src/table/src/requests.rs index 5ea032c7f9..4236bb1700 100644 --- a/src/table/src/requests.rs +++ b/src/table/src/requests.rs @@ -26,7 +26,9 @@ use common_time::range::TimestampRange; use common_time::TimeToLive; use datatypes::data_type::ConcreteDataType; use datatypes::prelude::VectorRef; -use datatypes::schema::{ColumnSchema, FulltextOptions, SkippingIndexOptions}; +use datatypes::schema::{ + ColumnDefaultConstraint, ColumnSchema, FulltextOptions, SkippingIndexOptions, +}; use greptime_proto::v1::region::compact_request; use serde::{Deserialize, Serialize}; use store_api::metric_engine_consts::{ @@ -289,6 +291,15 @@ pub enum AlterKind { DropDefaults { names: Vec, }, + SetDefaults { + defaults: Vec, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SetDefaultRequest { + pub column_name: String, + pub default_constraint: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/tests/cases/standalone/common/alter/alter_table_alter_column_drop_default.result b/tests/cases/standalone/common/alter/alter_table_alter_column_drop_default.result index a4ea729899..5b793e3506 100644 --- a/tests/cases/standalone/common/alter/alter_table_alter_column_drop_default.result +++ b/tests/cases/standalone/common/alter/alter_table_alter_column_drop_default.result @@ -51,11 +51,11 @@ SELECT * FROM test1; | 1 | 2024-01-30T12:00:00 | 100 | 200 | 2024-01-30T00:00:00 | 300 | +---+---------------------+-----+-----+---------------------+-----+ -ALTER TABLE test1 ALTER k DROP DEFAULT; +ALTER TABLE test1 MODIFY COLUMN k DROP DEFAULT; Affected Rows: 0 -ALTER TABLE test1 ALTER l DROP DEFAULT, ALTER m DROP DEFAULT, ALTER n DROP DEFAULT; +ALTER TABLE test1 MODIFY COLUMN l DROP DEFAULT, MODIFY COLUMN m DROP DEFAULT, MODIFY COLUMN n DROP DEFAULT; Affected Rows: 0 @@ -103,7 +103,7 @@ SELECT * FROM test1; | 1 | 2024-01-30T12:00:00 | | | | | 400 | +---+---------------------+---+---+---+---+-----+ -ALTER TABLE test1 ALTER o DROP DEFAULT; +ALTER TABLE test1 MODIFY COLUMN o DROP DEFAULT; Error: 1004(InvalidArguments), Invalid alter table(test1) request: column o is not nullable and `default` cannot be dropped diff --git a/tests/cases/standalone/common/alter/alter_table_alter_column_drop_default.sql b/tests/cases/standalone/common/alter/alter_table_alter_column_drop_default.sql index 4e0eeebc39..bff1243e42 100644 --- a/tests/cases/standalone/common/alter/alter_table_alter_column_drop_default.sql +++ b/tests/cases/standalone/common/alter/alter_table_alter_column_drop_default.sql @@ -13,8 +13,8 @@ INSERT INTO test1 VALUES (1, '2024-01-30 12:00:00', DEFAULT, DEFAULT, DEFAULT, D SELECT * FROM test1; -ALTER TABLE test1 ALTER k DROP DEFAULT; -ALTER TABLE test1 ALTER l DROP DEFAULT, ALTER m DROP DEFAULT, ALTER n DROP DEFAULT; +ALTER TABLE test1 MODIFY COLUMN k DROP DEFAULT; +ALTER TABLE test1 MODIFY COLUMN l DROP DEFAULT, MODIFY COLUMN m DROP DEFAULT, MODIFY COLUMN n DROP DEFAULT; SHOW CREATE TABLE test1; @@ -25,6 +25,6 @@ SELECT * FROM test1; ALTER TABLE test1 ADD COLUMN o INTEGER NOT NULL DEFAULT 400; SELECT * FROM test1; -ALTER TABLE test1 ALTER o DROP DEFAULT; +ALTER TABLE test1 MODIFY COLUMN o DROP DEFAULT; DROP TABLE test1; diff --git a/tests/cases/standalone/common/alter/alter_table_alter_column_set_default.result b/tests/cases/standalone/common/alter/alter_table_alter_column_set_default.result new file mode 100644 index 0000000000..30d82e1000 --- /dev/null +++ b/tests/cases/standalone/common/alter/alter_table_alter_column_set_default.result @@ -0,0 +1,128 @@ +--- alter table to add new column with default timestamp values aware of session timezone test --- +CREATE TABLE test1 (i INTEGER, j TIMESTAMP time index, PRIMARY KEY(i)); + +Affected Rows: 0 + +ALTER TABLE test1 ADD COLUMN k INTEGER DEFAULT 100; + +Affected Rows: 0 + +ALTER TABLE test1 ADD COLUMN l INTEGER DEFAULT 200; + +Affected Rows: 0 + +ALTER TABLE test1 ADD COLUMN m TIMESTAMP DEFAULT '2024-01-30 00:00:00'; + +Affected Rows: 0 + +ALTER TABLE test1 ADD COLUMN n INTEGER DEFAULT 300; + +Affected Rows: 0 + +--- set default value for k, l, n, n --- +ALTER TABLE test1 MODIFY COLUMN k SET DEFAULT 101; + +Affected Rows: 0 + +ALTER TABLE test1 MODIFY COLUMN l SET DEFAULT 201, MODIFY COLUMN m SET DEFAULT '2024-01-30 00:00:00', MODIFY COLUMN n SET DEFAULT 300; + +Affected Rows: 0 + +SHOW CREATE TABLE test1; + ++-------+-------------------------------------------------------------+ +| Table | Create Table | ++-------+-------------------------------------------------------------+ +| test1 | CREATE TABLE IF NOT EXISTS "test1" ( | +| | "i" INT NULL, | +| | "j" TIMESTAMP(3) NOT NULL, | +| | "k" INT NULL DEFAULT 101, | +| | "l" INT NULL DEFAULT 201, | +| | "m" TIMESTAMP(3) NULL DEFAULT '2024-01-30 00:00:00+0000', | +| | "n" INT NULL DEFAULT 300, | +| | TIME INDEX ("j"), | +| | PRIMARY KEY ("i") | +| | ) | +| | | +| | ENGINE=mito | +| | | ++-------+-------------------------------------------------------------+ + +INSERT INTO test1 VALUES (1, '2024-01-30 12:00:00', DEFAULT, DEFAULT, DEFAULT, DEFAULT); + +Affected Rows: 1 + +SELECT * FROM test1; + ++---+---------------------+-----+-----+---------------------+-----+ +| i | j | k | l | m | n | ++---+---------------------+-----+-----+---------------------+-----+ +| 1 | 2024-01-30T12:00:00 | 101 | 201 | 2024-01-30T00:00:00 | 300 | ++---+---------------------+-----+-----+---------------------+-----+ + +--- SET `CURRENT_TIMESTAMP` as default +ALTER TABLE test1 MODIFY COLUMN m SET DEFAULT CURRENT_TIMESTAMP; + +Affected Rows: 0 + +SHOW CREATE TABLE test1; + ++-------+------------------------------------------------------+ +| Table | Create Table | ++-------+------------------------------------------------------+ +| test1 | CREATE TABLE IF NOT EXISTS "test1" ( | +| | "i" INT NULL, | +| | "j" TIMESTAMP(3) NOT NULL, | +| | "k" INT NULL DEFAULT 101, | +| | "l" INT NULL DEFAULT 201, | +| | "m" TIMESTAMP(3) NULL DEFAULT current_timestamp(), | +| | "n" INT NULL DEFAULT 300, | +| | TIME INDEX ("j"), | +| | PRIMARY KEY ("i") | +| | ) | +| | | +| | ENGINE=mito | +| | | ++-------+------------------------------------------------------+ + +ALTER TABLE test1 ADD COLUMN o INTEGER NOT NULL DEFAULT 400; + +Affected Rows: 0 + +ALTER TABLE test1 MODIFY COLUMN o SET DEFAULT 401; + +Affected Rows: 0 + +SHOW CREATE TABLE test1; + ++-------+------------------------------------------------------+ +| Table | Create Table | ++-------+------------------------------------------------------+ +| test1 | CREATE TABLE IF NOT EXISTS "test1" ( | +| | "i" INT NULL, | +| | "j" TIMESTAMP(3) NOT NULL, | +| | "k" INT NULL DEFAULT 101, | +| | "l" INT NULL DEFAULT 201, | +| | "m" TIMESTAMP(3) NULL DEFAULT current_timestamp(), | +| | "n" INT NULL DEFAULT 300, | +| | "o" INT NOT NULL DEFAULT 401, | +| | TIME INDEX ("j"), | +| | PRIMARY KEY ("i") | +| | ) | +| | | +| | ENGINE=mito | +| | | ++-------+------------------------------------------------------+ + +ALTER TABLE test1 MODIFY COLUMN o SET DEFAULT "not allow"; + +Error: 1001(Unsupported), Unsupported expr in default constraint: "not allow" for column: o + +ALTER TABLE test1 MODIFY COLUMN o SET DEFAULT NULL; + +Error: 1004(InvalidArguments), Default value should not be null for non null column + +DROP TABLE test1; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/alter/alter_table_alter_column_set_default.sql b/tests/cases/standalone/common/alter/alter_table_alter_column_set_default.sql new file mode 100644 index 0000000000..a80d33ef18 --- /dev/null +++ b/tests/cases/standalone/common/alter/alter_table_alter_column_set_default.sql @@ -0,0 +1,35 @@ +--- alter table to add new column with default timestamp values aware of session timezone test --- + +CREATE TABLE test1 (i INTEGER, j TIMESTAMP time index, PRIMARY KEY(i)); + + +ALTER TABLE test1 ADD COLUMN k INTEGER DEFAULT 100; +ALTER TABLE test1 ADD COLUMN l INTEGER DEFAULT 200; +ALTER TABLE test1 ADD COLUMN m TIMESTAMP DEFAULT '2024-01-30 00:00:00'; +ALTER TABLE test1 ADD COLUMN n INTEGER DEFAULT 300; + +--- set default value for k, l, n, n --- +ALTER TABLE test1 MODIFY COLUMN k SET DEFAULT 101; +ALTER TABLE test1 MODIFY COLUMN l SET DEFAULT 201, MODIFY COLUMN m SET DEFAULT '2024-01-30 00:00:00', MODIFY COLUMN n SET DEFAULT 300; + +SHOW CREATE TABLE test1; + +INSERT INTO test1 VALUES (1, '2024-01-30 12:00:00', DEFAULT, DEFAULT, DEFAULT, DEFAULT); + +SELECT * FROM test1; + +--- SET `CURRENT_TIMESTAMP` as default +ALTER TABLE test1 MODIFY COLUMN m SET DEFAULT CURRENT_TIMESTAMP; +SHOW CREATE TABLE test1; + +ALTER TABLE test1 ADD COLUMN o INTEGER NOT NULL DEFAULT 400; + +ALTER TABLE test1 MODIFY COLUMN o SET DEFAULT 401; + +SHOW CREATE TABLE test1; + +ALTER TABLE test1 MODIFY COLUMN o SET DEFAULT "not allow"; + +ALTER TABLE test1 MODIFY COLUMN o SET DEFAULT NULL; + +DROP TABLE test1; diff --git a/tests/cases/standalone/common/create/current_timestamp.result b/tests/cases/standalone/common/create/current_timestamp.result index 19ed385981..09cb8a01a5 100644 --- a/tests/cases/standalone/common/create/current_timestamp.result +++ b/tests/cases/standalone/common/create/current_timestamp.result @@ -54,7 +54,7 @@ show create table t3; create table t4 (ts timestamp time index default now); -Error: 1001(Unsupported), Unsupported expr in default constraint: Identifier(Ident { value: "now", quote_style: None, span: Span(Location(1,50)..Location(1,53)) }) for column: ts +Error: 1001(Unsupported), Unsupported expr in default constraint: now for column: ts drop table t1;