feat: Add DROP DEFAULT (#6290)

* feat: Add `DROP DEFAULT`

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

* chore: use `next_token`

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-06-17 17:33:56 +08:00
committed by GitHub
parent 50e4c916e7
commit 438791b3e4
14 changed files with 474 additions and 11 deletions

2
Cargo.lock generated
View File

@@ -5143,7 +5143,7 @@ dependencies = [
[[package]]
name = "greptime-proto"
version = "0.1.0"
source = "git+https://github.com/GreptimeTeam/greptime-proto.git?rev=fdcbe5f1c7c467634c90a1fd1a00a784b92a4e80#fdcbe5f1c7c467634c90a1fd1a00a784b92a4e80"
source = "git+https://github.com/firefantasy/greptime-proto.git?rev=abaee5144a833a025f5bc0e41642ef733d9c5c98#abaee5144a833a025f5bc0e41642ef733d9c5c98"
dependencies = [
"prost 0.13.5",
"serde",

View File

@@ -134,7 +134,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 = "fdcbe5f1c7c467634c90a1fd1a00a784b92a4e80" }
greptime-proto = { git = "https://github.com/firefantasy/greptime-proto.git", rev = "abaee5144a833a025f5bc0e41642ef733d9c5c98" }
hex = "0.4"
http = "1"
humantime = "2.1"

View File

@@ -180,6 +180,22 @@ pub fn alter_expr_to_request(table_id: TableId, expr: AlterTableExpr) -> Result<
},
None => return MissingAlterIndexOptionSnafu.fail(),
},
Kind::DropDefaults(o) => {
let names = o
.drop_defaults
.into_iter()
.map(|col| {
ensure!(
!col.column_name.is_empty(),
MissingFieldSnafu {
field: "column_name"
}
);
Ok(col.column_name)
})
.collect::<Result<Vec<_>>>()?;
AlterKind::DropDefaults { names }
}
};
let request = AlterTableRequest {

View File

@@ -135,6 +135,7 @@ fn create_proto_alter_kind(
Kind::UnsetTableOptions(v) => Ok(Some(alter_request::Kind::UnsetTableOptions(v.clone()))),
Kind::SetIndex(v) => Ok(Some(alter_request::Kind::SetIndex(v.clone()))),
Kind::UnsetIndex(v) => Ok(Some(alter_request::Kind::UnsetIndex(v.clone()))),
Kind::DropDefaults(v) => Ok(Some(alter_request::Kind::DropDefaults(v.clone()))),
}
}

View File

@@ -61,7 +61,8 @@ impl AlterTableProcedure {
| AlterKind::SetTableOptions { .. }
| AlterKind::UnsetTableOptions { .. }
| AlterKind::SetIndex { .. }
| AlterKind::UnsetIndex { .. } => {}
| AlterKind::UnsetIndex { .. }
| AlterKind::DropDefaults { .. } => {}
}
Ok(new_info)

View File

@@ -24,11 +24,11 @@ use api::v1::column_def::options_from_column_schema;
use api::v1::{
set_index, unset_index, AddColumn, AddColumns, AlterDatabaseExpr, AlterTableExpr, Analyzer,
ColumnDataType, ColumnDataTypeExtension, CreateFlowExpr, CreateTableExpr, CreateViewExpr,
DropColumn, DropColumns, ExpireAfter, FulltextBackend as PbFulltextBackend, ModifyColumnType,
ModifyColumnTypes, RenameTable, SemanticType, SetDatabaseOptions, SetFulltext, SetIndex,
SetInverted, SetSkipping, SetTableOptions, SkippingIndexType as PbSkippingIndexType, TableName,
UnsetDatabaseOptions, UnsetFulltext, UnsetIndex, UnsetInverted, UnsetSkipping,
UnsetTableOptions,
DropColumn, DropColumns, DropDefaults, ExpireAfter, FulltextBackend as PbFulltextBackend,
ModifyColumnType, ModifyColumnTypes, RenameTable, SemanticType, SetDatabaseOptions,
SetFulltext, SetIndex, SetInverted, SetSkipping, SetTableOptions,
SkippingIndexType as PbSkippingIndexType, TableName, UnsetDatabaseOptions, UnsetFulltext,
UnsetIndex, UnsetInverted, UnsetSkipping, UnsetTableOptions,
};
use common_error::ext::BoxedError;
use common_grpc_expr::util::ColumnExpr;
@@ -631,6 +631,17 @@ pub(crate) fn to_alter_table_expr(
})),
},
}),
AlterTableOperation::DropDefaults { columns } => {
AlterTableKind::DropDefaults(DropDefaults {
drop_defaults: columns
.into_iter()
.map(|col| {
let column_name = col.0.to_string();
Ok(api::v1::DropDefault { column_name })
})
.collect::<Result<Vec<_>>>()?,
})
}
};
Ok(AlterTableExpr {

View File

@@ -30,7 +30,7 @@ use crate::parsers::utils::{
};
use crate::statements::alter::{
AddColumn, AlterDatabase, AlterDatabaseOperation, AlterTable, AlterTableOperation,
KeyValueOption, SetIndexOperation, UnsetIndexOperation,
DropDefaultsOperation, KeyValueOption, SetIndexOperation, UnsetIndexOperation,
};
use crate::statements::statement::Statement;
use crate::util::parse_option_string;
@@ -156,6 +156,7 @@ 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(),
@@ -168,6 +169,29 @@ 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
@@ -198,6 +222,14 @@ 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_modify(&mut self) -> Result<AlterTableOperation> {
let _ = self.parser.next_token();
self.parser
@@ -385,6 +417,23 @@ impl ParserContext<'_> {
}
}
fn parse_alter_column_drop_default(
parser: &mut Parser,
) -> std::result::Result<DropDefaultsOperation, ParserError> {
parser.next_token();
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}`"
)))
}
}
/// Parses a string literal and an optional string literal value.
fn parse_string_options(parser: &mut Parser) -> std::result::Result<(String, String), ParserError> {
let name = parser.parse_literal_string()?;
@@ -1125,4 +1174,42 @@ mod tests {
}
}
}
#[test]
fn test_parse_alter_drop_default() {
let columns = vec![vec!["a"], vec!["a", "b", "c"]];
for col in columns {
let sql = col
.iter()
.map(|x| format!("ALTER {x} DROP DEFAULT"))
.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::DropDefaults { columns } => {
assert_eq!(col.len(), columns.len());
for i in 0..columns.len() {
assert_eq!(col[i], columns[i].0.value);
}
}
_ => unreachable!(),
}
}
_ => unreachable!(),
}
}
}
}

View File

@@ -92,8 +92,15 @@ pub enum AlterTableOperation {
UnsetIndex {
options: UnsetIndexOperation,
},
DropDefaults {
columns: Vec<DropDefaultsOperation>,
},
}
#[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 enum SetIndexOperation {
/// `MODIFY COLUMN <column_name> SET FULLTEXT INDEX [WITH <options>]`
@@ -204,6 +211,13 @@ impl Display for AlterTableOperation {
write!(f, "MODIFY COLUMN {column_name} UNSET SKIPPING INDEX")
}
},
AlterTableOperation::DropDefaults { columns } => {
let columns = columns
.iter()
.map(|column| format!("ALTER {} DROP DEFAULT", column.0))
.join(", ");
write!(f, "{columns}")
}
}
}
}
@@ -487,5 +501,26 @@ ALTER TABLE monitor MODIFY COLUMN a SET INVERTED INDEX"#,
unreachable!();
}
}
let sql = "ALTER TABLE monitor ALTER a DROP DEFAULT";
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 ALTER a DROP DEFAULT"#,
&new_sql
);
}
_ => {
unreachable!();
}
}
}
}

View File

@@ -612,6 +612,9 @@ impl RegionMetadataBuilder {
AlterKind::UnsetRegionOptions { keys: _ } => {
// nothing to be done with RegionMetadata
}
AlterKind::DropDefaults { names } => {
self.drop_defaults(names)?;
}
}
Ok(self)
}
@@ -826,6 +829,40 @@ impl RegionMetadataBuilder {
}
Ok(())
}
fn drop_defaults(&mut self, column_names: Vec<String>) -> Result<()> {
for name in column_names.iter() {
let meta = self
.column_metadatas
.iter_mut()
.find(|col| col.column_schema.name == *name);
if let Some(meta) = meta {
if !meta.column_schema.is_nullable() {
return InvalidRegionRequestSnafu {
region_id: self.region_id,
err: format!(
"column {name} is not nullable and `default` cannot be dropped",
),
}
.fail();
}
meta.column_schema = meta
.column_schema
.clone()
.with_default_constraint(None)
.with_context(|_| CastDefaultValueSnafu {
reason: format!("Failed to drop default : {name:?}"),
})?;
} else {
return InvalidRegionRequestSnafu {
region_id: self.region_id,
err: format!("column {name} not found",),
}
.fail();
}
}
Ok(())
}
}
/// Fields skipped in serialization.
@@ -1100,7 +1137,10 @@ fn unset_column_fulltext_options(
#[cfg(test)]
mod test {
use datatypes::prelude::ConcreteDataType;
use datatypes::schema::{ColumnSchema, FulltextAnalyzer, FulltextBackend};
use datatypes::schema::{
ColumnDefaultConstraint, ColumnSchema, FulltextAnalyzer, FulltextBackend,
};
use datatypes::value::Value;
use super::*;
@@ -1423,6 +1463,19 @@ mod test {
assert_eq!(names, actual);
}
fn get_columns_default_constraint(
metadata: &RegionMetadata,
name: String,
) -> Option<Option<&ColumnDefaultConstraint>> {
metadata.column_metadatas.iter().find_map(|col| {
if col.column_schema.name == name {
Some(col.column_schema.default_constraint())
} else {
None
}
})
}
#[test]
fn test_alter() {
// a (tag), b (field), c (ts)
@@ -1500,6 +1553,50 @@ mod test {
let err = builder.build().unwrap_err();
assert_eq!(StatusCode::InvalidArguments, err.status_code());
let mut builder: RegionMetadataBuilder = RegionMetadataBuilder::from_existing(metadata);
let mut column_metadata = new_column_metadata("g", false, 8);
let default_constraint = Some(ColumnDefaultConstraint::Value(Value::from("g")));
column_metadata.column_schema = column_metadata
.column_schema
.with_default_constraint(default_constraint.clone())
.unwrap();
builder
.alter(AlterKind::AddColumns {
columns: vec![AddColumn {
column_metadata,
location: None,
}],
})
.unwrap();
let metadata = builder.build().unwrap();
assert_eq!(
get_columns_default_constraint(&metadata, "g".to_string()).unwrap(),
default_constraint.as_ref()
);
check_columns(&metadata, &["a", "b", "f", "c", "d", "g"]);
let mut builder: RegionMetadataBuilder = RegionMetadataBuilder::from_existing(metadata);
builder
.alter(AlterKind::DropDefaults {
names: vec!["g".to_string()],
})
.unwrap();
let metadata = builder.build().unwrap();
assert_eq!(
get_columns_default_constraint(&metadata, "g".to_string()).unwrap(),
None
);
check_columns(&metadata, &["a", "b", "f", "c", "d", "g"]);
let mut builder: RegionMetadataBuilder = RegionMetadataBuilder::from_existing(metadata);
builder
.alter(AlterKind::DropColumns {
names: vec!["g".to_string()],
})
.unwrap();
let metadata = builder.build().unwrap();
check_columns(&metadata, &["a", "b", "f", "c", "d"]);
let mut builder = RegionMetadataBuilder::from_existing(metadata);
builder
.alter(AlterKind::ModifyColumnTypes {

View File

@@ -530,6 +530,11 @@ pub enum AlterKind {
SetIndex { options: ApiSetIndexOptions },
/// Unset index options.
UnsetIndex { options: ApiUnsetIndexOptions },
/// Drop column default value.
DropDefaults {
/// Name of columns to drop.
names: Vec<String>,
},
}
#[derive(Debug, PartialEq, Eq, Clone)]
@@ -627,6 +632,11 @@ impl AlterKind {
options.is_fulltext(),
)?;
}
AlterKind::DropDefaults { names } => {
names
.iter()
.try_for_each(|name| Self::validate_column_to_drop(name, metadata))?;
}
}
Ok(())
}
@@ -656,6 +666,9 @@ impl AlterKind {
AlterKind::UnsetIndex { options } => {
metadata.column_by_name(options.column_name()).is_some()
}
AlterKind::DropDefaults { names } => names
.iter()
.any(|name| metadata.column_by_name(name).is_some()),
}
}
@@ -794,6 +807,9 @@ 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(),
},
};
Ok(alter_kind)

View File

@@ -264,6 +264,7 @@ impl TableMeta {
self.change_column_skipping_index_options(table_name, column_name, None)
}
},
AlterKind::DropDefaults { names } => self.drop_defaults(table_name, names),
}
}
@@ -926,8 +927,60 @@ impl TableMeta {
column_names,
})
}
}
fn drop_defaults(&self, table_name: &str, column_names: &[String]) -> 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(name) = column_names.iter().find(|s| **s == column_schema.name) {
// Drop default constraint.
ensure!(
column_schema.default_constraint().is_some(),
error::InvalidAlterRequestSnafu {
table: table_name,
err: format!("column {name} does not have a default value"),
}
);
if !column_schema.is_nullable() {
return error::InvalidAlterRequestSnafu {
table: table_name,
err: format!(
"column {name} is not nullable and `default` cannot be dropped",
),
}
.fail();
}
let new_column_schema = column_schema.clone();
let new_column_schema = new_column_schema
.with_default_constraint(None)
.with_context(|_| error::SchemaBuildSnafu {
msg: format!("Table {table_name} cannot drop 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 drop default values"),
})?;
let _ = meta_builder.schema(Arc::new(new_schema));
Ok(meta_builder)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Builder)]
#[builder(pattern = "owned")]
pub struct TableInfo {

View File

@@ -257,6 +257,9 @@ pub enum AlterKind {
UnsetIndex {
options: UnsetIndexOptions,
},
DropDefaults {
names: Vec<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]

View File

@@ -0,0 +1,113 @@
--- 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
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 100, |
| | "l" INT NULL DEFAULT 200, |
| | "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 | 100 | 200 | 2024-01-30T00:00:00 | 300 |
+---+---------------------+-----+-----+---------------------+-----+
ALTER TABLE test1 ALTER k DROP DEFAULT;
Affected Rows: 0
ALTER TABLE test1 ALTER l DROP DEFAULT, ALTER m DROP DEFAULT, ALTER n DROP DEFAULT;
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, |
| | "l" INT NULL, |
| | "m" TIMESTAMP(3) NULL, |
| | "n" INT NULL, |
| | 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 | | | | |
+---+---------------------+---+---+---+---+
ALTER TABLE test1 ADD COLUMN o INTEGER NOT NULL DEFAULT 400;
Affected Rows: 0
SELECT * FROM test1;
+---+---------------------+---+---+---+---+-----+
| i | j | k | l | m | n | o |
+---+---------------------+---+---+---+---+-----+
| 1 | 2024-01-30T12:00:00 | | | | | 400 |
+---+---------------------+---+---+---+---+-----+
ALTER TABLE test1 ALTER o DROP DEFAULT;
Error: 1004(InvalidArguments), Invalid alter table(test1) request: column o is not nullable and `default` cannot be dropped
DROP TABLE test1;
Affected Rows: 0

View File

@@ -0,0 +1,30 @@
--- 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;
SHOW CREATE TABLE test1;
INSERT INTO test1 VALUES (1, '2024-01-30 12:00:00', DEFAULT, DEFAULT, DEFAULT, DEFAULT);
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;
SHOW CREATE TABLE test1;
INSERT INTO test1 VALUES (1, '2024-01-30 12:00:00', DEFAULT, DEFAULT, DEFAULT, DEFAULT);
SELECT * FROM test1;
ALTER TABLE test1 ADD COLUMN o INTEGER NOT NULL DEFAULT 400;
SELECT * FROM test1;
ALTER TABLE test1 ALTER o DROP DEFAULT;
DROP TABLE test1;