feat: support add if not exists in the gRPC alter kind (#5273)

* test: test adding existing columns

* chore: add more checks to AlterKind

* chore: update logs

* fix: check and build table info first

* feat: Add add_if_not_exists flag to alter expr

* feat: skip existing columns when building alter kind

* checks in make_region_alter_kind()
* reuse the alter kind

* test: fix tests in common-meta

* chore: fix typos

* chore: update comments
This commit is contained in:
Yingwen
2025-01-03 15:23:17 +08:00
committed by GitHub
parent d20b592fe8
commit 89399131dd
22 changed files with 633 additions and 142 deletions

View File

@@ -194,12 +194,9 @@ impl TableMeta {
&self,
table_name: &str,
alter_kind: &AlterKind,
add_if_not_exists: bool,
) -> Result<TableMetaBuilder> {
match alter_kind {
AlterKind::AddColumns { columns } => {
self.add_columns(table_name, columns, add_if_not_exists)
}
AlterKind::AddColumns { columns } => self.add_columns(table_name, columns),
AlterKind::DropColumns { names } => self.remove_columns(table_name, names),
AlterKind::ModifyColumnTypes { columns } => {
self.modify_column_types(table_name, columns)
@@ -340,6 +337,7 @@ impl TableMeta {
Ok(meta_builder)
}
// TODO(yingwen): Remove this.
/// Allocate a new column for the table.
///
/// This method would bump the `next_column_id` of the meta.
@@ -384,11 +382,11 @@ impl TableMeta {
builder
}
// TODO(yingwen): Tests add if not exists.
fn add_columns(
&self,
table_name: &str,
requests: &[AddColumnRequest],
add_if_not_exists: bool,
) -> Result<TableMetaBuilder> {
let table_schema = &self.schema;
let mut meta_builder = self.new_meta_builder();
@@ -396,63 +394,61 @@ impl TableMeta {
self.primary_key_indices.iter().collect();
let mut names = HashSet::with_capacity(requests.len());
let mut new_requests = Vec::with_capacity(requests.len());
let requests = if add_if_not_exists {
for col_to_add in requests {
if let Some(column_schema) =
table_schema.column_schema_by_name(&col_to_add.column_schema.name)
{
// If the column already exists, we should check if the type is the same.
ensure!(
column_schema.data_type == col_to_add.column_schema.data_type,
error::InvalidAlterRequestSnafu {
table: table_name,
err: format!(
"column {} already exists with different type",
col_to_add.column_schema.name
),
}
);
} else {
new_requests.push(col_to_add.clone());
}
}
&new_requests[..]
} else {
requests
};
let mut new_columns = Vec::with_capacity(requests.len());
for col_to_add in requests {
ensure!(
names.insert(&col_to_add.column_schema.name),
error::InvalidAlterRequestSnafu {
table: table_name,
err: format!(
"add column {} more than once",
col_to_add.column_schema.name
),
}
);
if let Some(column_schema) =
table_schema.column_schema_by_name(&col_to_add.column_schema.name)
{
// If the column already exists.
ensure!(
col_to_add.add_if_not_exists,
error::ColumnExistsSnafu {
table_name,
column_name: &col_to_add.column_schema.name
},
);
ensure!(
!table_schema.contains_column(&col_to_add.column_schema.name),
error::ColumnExistsSnafu {
table_name,
column_name: col_to_add.column_schema.name.to_string()
},
);
// Checks if the type is the same
ensure!(
column_schema.data_type == col_to_add.column_schema.data_type,
error::InvalidAlterRequestSnafu {
table: table_name,
err: format!(
"column {} already exists with different type {:?}",
col_to_add.column_schema.name, column_schema.data_type,
),
}
);
} else {
// A new column.
// Ensures we only add a column once.
ensure!(
names.insert(&col_to_add.column_schema.name),
error::InvalidAlterRequestSnafu {
table: table_name,
err: format!(
"add column {} more than once",
col_to_add.column_schema.name
),
}
);
ensure!(
col_to_add.column_schema.is_nullable()
|| col_to_add.column_schema.default_constraint().is_some(),
error::InvalidAlterRequestSnafu {
table: table_name,
err: format!(
"no default value for column {}",
col_to_add.column_schema.name
),
},
);
ensure!(
col_to_add.column_schema.is_nullable()
|| col_to_add.column_schema.default_constraint().is_some(),
error::InvalidAlterRequestSnafu {
table: table_name,
err: format!(
"no default value for column {}",
col_to_add.column_schema.name
),
},
);
new_columns.push(col_to_add.clone());
}
}
let requests = &new_columns[..];
let SplitResult {
columns_at_first,
@@ -881,6 +877,7 @@ pub struct RawTableMeta {
pub value_indices: Vec<usize>,
/// Engine type of this table. Usually in small case.
pub engine: String,
/// Next column id of a new column.
/// Deprecated. See https://github.com/GreptimeTeam/greptimedb/issues/2982
pub next_column_id: ColumnId,
pub region_numbers: Vec<u32>,
@@ -1078,6 +1075,7 @@ mod tests {
use super::*;
/// Create a test schema with 3 columns: `[col1 int32, ts timestampmills, col2 int32]`.
fn new_test_schema() -> Schema {
let column_schemas = vec![
ColumnSchema::new("col1", ConcreteDataType::int32_datatype(), true),
@@ -1129,17 +1127,19 @@ mod tests {
column_schema: new_tag,
is_key: true,
location: None,
add_if_not_exists: false,
},
AddColumnRequest {
column_schema: new_field,
is_key: false,
location: None,
add_if_not_exists: false,
},
],
};
let builder = meta
.builder_with_alter_kind("my_table", &alter_kind, false)
.builder_with_alter_kind("my_table", &alter_kind)
.unwrap();
builder.build().unwrap()
}
@@ -1157,6 +1157,7 @@ mod tests {
column_schema: new_tag,
is_key: true,
location: Some(AddColumnLocation::First),
add_if_not_exists: false,
},
AddColumnRequest {
column_schema: new_field,
@@ -1164,12 +1165,13 @@ mod tests {
location: Some(AddColumnLocation::After {
column_name: "ts".to_string(),
}),
add_if_not_exists: false,
},
],
};
let builder = meta
.builder_with_alter_kind("my_table", &alter_kind, false)
.builder_with_alter_kind("my_table", &alter_kind)
.unwrap();
builder.build().unwrap()
}
@@ -1199,6 +1201,48 @@ mod tests {
assert_eq!(&[1, 2, 4], &new_meta.value_indices[..]);
}
#[test]
fn test_add_columns_multiple_times() {
let schema = Arc::new(new_test_schema());
let meta = TableMetaBuilder::default()
.schema(schema)
.primary_key_indices(vec![0])
.engine("engine")
.next_column_id(3)
.build()
.unwrap();
let alter_kind = AlterKind::AddColumns {
columns: vec![
AddColumnRequest {
column_schema: ColumnSchema::new(
"col3",
ConcreteDataType::int32_datatype(),
true,
),
is_key: true,
location: None,
add_if_not_exists: true,
},
AddColumnRequest {
column_schema: ColumnSchema::new(
"col3",
ConcreteDataType::int32_datatype(),
true,
),
is_key: true,
location: None,
add_if_not_exists: true,
},
],
};
let err = meta
.builder_with_alter_kind("my_table", &alter_kind)
.err()
.unwrap();
assert_eq!(StatusCode::InvalidArguments, err.status_code());
}
#[test]
fn test_remove_columns() {
let schema = Arc::new(new_test_schema());
@@ -1216,7 +1260,7 @@ mod tests {
names: vec![String::from("col2"), String::from("my_field")],
};
let new_meta = meta
.builder_with_alter_kind("my_table", &alter_kind, false)
.builder_with_alter_kind("my_table", &alter_kind)
.unwrap()
.build()
.unwrap();
@@ -1271,7 +1315,7 @@ mod tests {
names: vec![String::from("col3"), String::from("col1")],
};
let new_meta = meta
.builder_with_alter_kind("my_table", &alter_kind, false)
.builder_with_alter_kind("my_table", &alter_kind)
.unwrap()
.build()
.unwrap();
@@ -1307,14 +1351,62 @@ mod tests {
column_schema: ColumnSchema::new("col1", ConcreteDataType::string_datatype(), true),
is_key: false,
location: None,
add_if_not_exists: false,
}],
};
let err = meta
.builder_with_alter_kind("my_table", &alter_kind, false)
.builder_with_alter_kind("my_table", &alter_kind)
.err()
.unwrap();
assert_eq!(StatusCode::TableColumnExists, err.status_code());
// Add if not exists
let alter_kind = AlterKind::AddColumns {
columns: vec![AddColumnRequest {
column_schema: ColumnSchema::new("col1", ConcreteDataType::int32_datatype(), true),
is_key: true,
location: None,
add_if_not_exists: true,
}],
};
let new_meta = meta
.builder_with_alter_kind("my_table", &alter_kind)
.unwrap()
.build()
.unwrap();
assert_eq!(
meta.schema.column_schemas(),
new_meta.schema.column_schemas()
);
assert_eq!(meta.schema.version() + 1, new_meta.schema.version());
}
#[test]
fn test_add_different_type_column() {
let schema = Arc::new(new_test_schema());
let meta = TableMetaBuilder::default()
.schema(schema)
.primary_key_indices(vec![0])
.engine("engine")
.next_column_id(3)
.build()
.unwrap();
// Add if not exists, but different type.
let alter_kind = AlterKind::AddColumns {
columns: vec![AddColumnRequest {
column_schema: ColumnSchema::new("col1", ConcreteDataType::string_datatype(), true),
is_key: false,
location: None,
add_if_not_exists: true,
}],
};
let err = meta
.builder_with_alter_kind("my_table", &alter_kind)
.err()
.unwrap();
assert_eq!(StatusCode::InvalidArguments, err.status_code());
}
#[test]
@@ -1328,6 +1420,7 @@ mod tests {
.build()
.unwrap();
// Not nullable and no default value.
let alter_kind = AlterKind::AddColumns {
columns: vec![AddColumnRequest {
column_schema: ColumnSchema::new(
@@ -1337,11 +1430,12 @@ mod tests {
),
is_key: false,
location: None,
add_if_not_exists: false,
}],
};
let err = meta
.builder_with_alter_kind("my_table", &alter_kind, false)
.builder_with_alter_kind("my_table", &alter_kind)
.err()
.unwrap();
assert_eq!(StatusCode::InvalidArguments, err.status_code());
@@ -1363,7 +1457,7 @@ mod tests {
};
let err = meta
.builder_with_alter_kind("my_table", &alter_kind, false)
.builder_with_alter_kind("my_table", &alter_kind)
.err()
.unwrap();
assert_eq!(StatusCode::TableColumnNotFound, err.status_code());
@@ -1388,7 +1482,7 @@ mod tests {
};
let err = meta
.builder_with_alter_kind("my_table", &alter_kind, false)
.builder_with_alter_kind("my_table", &alter_kind)
.err()
.unwrap();
assert_eq!(StatusCode::TableColumnNotFound, err.status_code());
@@ -1411,7 +1505,7 @@ mod tests {
};
let err = meta
.builder_with_alter_kind("my_table", &alter_kind, false)
.builder_with_alter_kind("my_table", &alter_kind)
.err()
.unwrap();
assert_eq!(StatusCode::InvalidArguments, err.status_code());
@@ -1422,7 +1516,7 @@ mod tests {
};
let err = meta
.builder_with_alter_kind("my_table", &alter_kind, false)
.builder_with_alter_kind("my_table", &alter_kind)
.err()
.unwrap();
assert_eq!(StatusCode::InvalidArguments, err.status_code());
@@ -1448,7 +1542,7 @@ mod tests {
};
let err = meta
.builder_with_alter_kind("my_table", &alter_kind, false)
.builder_with_alter_kind("my_table", &alter_kind)
.err()
.unwrap();
assert_eq!(StatusCode::InvalidArguments, err.status_code());
@@ -1462,7 +1556,7 @@ mod tests {
};
let err = meta
.builder_with_alter_kind("my_table", &alter_kind, false)
.builder_with_alter_kind("my_table", &alter_kind)
.err()
.unwrap();
assert_eq!(StatusCode::InvalidArguments, err.status_code());
@@ -1531,7 +1625,7 @@ mod tests {
options: FulltextOptions::default(),
};
let err = meta
.builder_with_alter_kind("my_table", &alter_kind, false)
.builder_with_alter_kind("my_table", &alter_kind)
.err()
.unwrap();
assert_eq!(
@@ -1552,7 +1646,7 @@ mod tests {
},
};
let new_meta = new_meta
.builder_with_alter_kind("my_table", &alter_kind, false)
.builder_with_alter_kind("my_table", &alter_kind)
.unwrap()
.build()
.unwrap();
@@ -1572,7 +1666,7 @@ mod tests {
column_name: "my_tag_first".to_string(),
};
let new_meta = new_meta
.builder_with_alter_kind("my_table", &alter_kind, false)
.builder_with_alter_kind("my_table", &alter_kind)
.unwrap()
.build()
.unwrap();

View File

@@ -185,6 +185,8 @@ pub struct AddColumnRequest {
pub column_schema: ColumnSchema,
pub is_key: bool,
pub location: Option<AddColumnLocation>,
/// Add column if not exists.
pub add_if_not_exists: bool,
}
/// Change column datatype request