feat: add SET DEFAULT syntax (#6421)

* feat: add `SET DEFAULT` syntax

Signed-off-by: Yihai Lin <yihai-lin@foxmail.com>

* test: add `CURRENT_TIMESTAMP()` as default value for `SET DEFAULT` syntax

Signed-off-by: Yihai Lin <yihai-lin@foxmail.com>

* refactor: Make the error types more precise.

Signed-off-by: Yihai Lin <yihai-lin@foxmail.com>

* chore: a minor error display enchancement for `SET DEFAULT`

Signed-off-by: Yihai Lin <yihai-lin@foxmail.com>

* refactor: Using `MODIFY COLUMN` for `DROP/SET DEFUALT`

Signed-off-by: Yihai Lin <yihai-lin@foxmail.com>

* chore: update `greptime-proto`

Signed-off-by: Yihai Lin <yihai-lin@foxmail.com>

---------

Signed-off-by: Yihai Lin <yihai-lin@foxmail.com>
This commit is contained in:
Lin Yihai
2025-07-29 14:41:02 +08:00
committed by GitHub
parent 8fef177575
commit b6cef77a5c
27 changed files with 680 additions and 83 deletions

8
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -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

View File

@@ -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<Uns
}
/// Convert an [`AlterTableExpr`] to an [`AlterTableRequest`]
pub fn alter_expr_to_request(table_id: TableId, expr: AlterTableExpr) -> Result<AlterTableRequest> {
///
/// 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<AlterTableRequest> {
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::<Result<Vec<_>>>()?;
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::<Result<Vec<_>>>()?;
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);

View File

@@ -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<T> = std::result::Result<T, Error>;
@@ -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,
}
}

View File

@@ -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)

View File

@@ -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!(

View File

@@ -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()))),
}
}

View File

@@ -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

View File

@@ -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<R: FromStr + std::fmt::Debug>(n: &str) -> Result<R>
@@ -368,6 +369,27 @@ pub(crate) fn parse_hex_string(s: &str) -> Result<Value> {
}
}
/// Deserialize default constraint from json bytes
pub fn deserialize_default_constraint(
bytes: &[u8],
column_name: &str,
data_type: &ConcreteDataType,
) -> Result<Option<ColumnDefaultConstraint>> {
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;

View File

@@ -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,
}

View File

@@ -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::<Result<Vec<_>>>()?,
})
}
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::<Result<Vec<_>>>()?,
}),
};
Ok(AlterTableExpr {

View File

@@ -1591,7 +1591,8 @@ pub fn verify_alter(
expr: AlterTableExpr,
) -> Result<bool> {
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,

View File

@@ -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<AlterTableOperation> {
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<AlterTableOperation> {
let _ = self.parser.next_token();
let keys = self
@@ -222,12 +199,43 @@ impl ParserContext<'_> {
}
}
fn parse_alter_table_drop_default(&mut self) -> Result<AlterTableOperation> {
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<AlterTableOperation> {
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<AlterTableOperation> {
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<AlterTableOperation> {
@@ -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<DropDefaultsOperation, ParserError> {
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<SetDefaultsOperation, ParserError> {
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::<Vec<String>>()
.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::<Vec<String>>()
.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`"
);
}
}

View File

@@ -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<DropDefaultsOperation>,
},
/// `ALTER <column_name> SET DEFAULT <default_value>`
SetDefaults {
defaults: Vec<SetDefaultsOperation>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut, Serialize)]
/// `ALTER <column_name> 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 <column_name> SET FULLTEXT INDEX [WITH <options>]`
@@ -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
);
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -562,6 +562,16 @@ pub enum AlterKind {
/// Name of columns to drop.
names: Vec<String>,
},
/// Set column default value.
SetDefaults {
/// Columns to change.
columns: Vec<SetDefault>,
},
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct SetDefault {
pub name: String,
pub default_constraint: Vec<u8>,
}
#[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<alter_request::Kind> for AlterKind {
@@ -882,6 +912,18 @@ impl TryFrom<alter_request::Kind> 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::<Result<Vec<_>>>()?,
},
};
Ok(alter_kind)

View File

@@ -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

View File

@@ -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()
}

View File

@@ -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<TableMetaBuilder> {
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)]

View File

@@ -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<String>,
},
SetDefaults {
defaults: Vec<SetDefaultRequest>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SetDefaultRequest {
pub column_name: String,
pub default_constraint: Option<ColumnDefaultConstraint>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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;