mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2025-12-22 22:20:02 +00:00
test: add fuzz tests for column data type alteration (#4076)
* feat: support make fuzz-stable in Makefile * test: add fuzz tests for column data type alteration * fix: optimize code by cr
This commit is contained in:
@@ -45,6 +45,7 @@ sqlx = { version = "0.6", features = [
|
||||
"postgres",
|
||||
"chrono",
|
||||
] }
|
||||
strum.workspace = true
|
||||
tinytemplate = "1.2"
|
||||
tokio = { workspace = true }
|
||||
|
||||
|
||||
@@ -129,6 +129,12 @@ impl TableContext {
|
||||
self.name = new_table_name;
|
||||
Ok(self)
|
||||
}
|
||||
AlterTableOperation::ModifyDataType { column } => {
|
||||
if let Some(idx) = self.columns.iter().position(|col| col.name == column.name) {
|
||||
self.columns[idx].column_type = column.column_type;
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ use crate::generator::{ColumnOptionGenerator, ConcreteDataTypeGenerator, Generat
|
||||
use crate::ir::alter_expr::{AlterTableExpr, AlterTableOperation};
|
||||
use crate::ir::create_expr::ColumnOption;
|
||||
use crate::ir::{
|
||||
droppable_columns, generate_columns, generate_random_value, ColumnTypeGenerator, Ident,
|
||||
droppable_columns, generate_columns, generate_random_value, modifiable_columns, Column,
|
||||
ColumnTypeGenerator, Ident,
|
||||
};
|
||||
|
||||
fn add_column_options_generator<R: Rng>(
|
||||
@@ -157,6 +158,39 @@ impl<R: Rng> Generator<AlterTableExpr, R> for AlterExprRenameGenerator<R> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the [AlterTableOperation::ModifyDataType] of [AlterTableExpr].
|
||||
#[derive(Builder)]
|
||||
#[builder(pattern = "owned")]
|
||||
pub struct AlterExprModifyDataTypeGenerator<R: Rng> {
|
||||
table_ctx: TableContextRef,
|
||||
#[builder(default = "Box::new(ColumnTypeGenerator)")]
|
||||
column_type_generator: ConcreteDataTypeGenerator<R>,
|
||||
}
|
||||
|
||||
impl<R: Rng> Generator<AlterTableExpr, R> for AlterExprModifyDataTypeGenerator<R> {
|
||||
type Error = Error;
|
||||
|
||||
fn generate(&self, rng: &mut R) -> Result<AlterTableExpr> {
|
||||
let modifiable = modifiable_columns(&self.table_ctx.columns);
|
||||
let changed = modifiable[rng.gen_range(0..modifiable.len())].clone();
|
||||
let mut to_type = self.column_type_generator.gen(rng);
|
||||
while !changed.column_type.can_arrow_type_cast_to(&to_type) {
|
||||
to_type = self.column_type_generator.gen(rng);
|
||||
}
|
||||
|
||||
Ok(AlterTableExpr {
|
||||
table_name: self.table_ctx.name.clone(),
|
||||
alter_options: AlterTableOperation::ModifyDataType {
|
||||
column: Column {
|
||||
name: changed.name,
|
||||
column_type: to_type,
|
||||
options: vec![],
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
@@ -200,7 +234,7 @@ mod tests {
|
||||
assert_eq!(expected, serialized);
|
||||
|
||||
let expr = AlterExprDropColumnGeneratorBuilder::default()
|
||||
.table_ctx(table_ctx)
|
||||
.table_ctx(table_ctx.clone())
|
||||
.build()
|
||||
.unwrap()
|
||||
.generate(&mut rng)
|
||||
@@ -208,5 +242,15 @@ mod tests {
|
||||
let serialized = serde_json::to_string(&expr).unwrap();
|
||||
let expected = r#"{"table_name":{"value":"animI","quote_style":null},"alter_options":{"DropColumn":{"name":{"value":"cUmquE","quote_style":null}}}}"#;
|
||||
assert_eq!(expected, serialized);
|
||||
|
||||
let expr = AlterExprModifyDataTypeGeneratorBuilder::default()
|
||||
.table_ctx(table_ctx)
|
||||
.build()
|
||||
.unwrap()
|
||||
.generate(&mut rng)
|
||||
.unwrap();
|
||||
let serialized = serde_json::to_string(&expr).unwrap();
|
||||
let expected = r#"{"table_name":{"value":"animI","quote_style":null},"alter_options":{"ModifyDataType":{"column":{"name":{"value":"toTAm","quote_style":null},"column_type":{"Int64":{}},"options":[]}}}}"#;
|
||||
assert_eq!(expected, serialized);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,6 +355,20 @@ pub fn droppable_columns(columns: &[Column]) -> Vec<&Column> {
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Returns columns that can use the alter table modify command
|
||||
pub fn modifiable_columns(columns: &[Column]) -> Vec<&Column> {
|
||||
columns
|
||||
.iter()
|
||||
.filter(|column| {
|
||||
!column.options.iter().any(|option| {
|
||||
option == &ColumnOption::PrimaryKey
|
||||
|| option == &ColumnOption::TimeIndex
|
||||
|| option == &ColumnOption::NotNull
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Generates [ColumnOption] for [Column].
|
||||
pub fn column_options_generator<R: Rng>(
|
||||
rng: &mut R,
|
||||
|
||||
@@ -35,4 +35,6 @@ pub enum AlterTableOperation {
|
||||
DropColumn { name: Ident },
|
||||
/// `RENAME <new_table_name>`
|
||||
RenameTable { new_table_name: Ident },
|
||||
/// `MODIFY COLUMN <column_name> <column_type>`
|
||||
ModifyDataType { column: Column },
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ impl DslTranslator<AlterTableExpr, String> for AlterTableExprTranslator {
|
||||
AlterTableOperation::RenameTable { new_table_name } => {
|
||||
Self::format_rename(&input.table_name, new_table_name)
|
||||
}
|
||||
AlterTableOperation::ModifyDataType { column } => {
|
||||
Self::format_modify_data_type(&input.table_name, column)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -72,6 +75,13 @@ impl AlterTableExprTranslator {
|
||||
)
|
||||
}
|
||||
|
||||
fn format_modify_data_type(name: impl Display, column: &Column) -> String {
|
||||
format!(
|
||||
"ALTER TABLE {name} MODIFY COLUMN {};",
|
||||
Self::format_column(column)
|
||||
)
|
||||
}
|
||||
|
||||
fn format_location(location: &Option<AddColumnLocation>) -> Option<String> {
|
||||
location.as_ref().map(|location| match location {
|
||||
AddColumnLocation::First => "FIRST".to_string(),
|
||||
@@ -155,5 +165,19 @@ mod tests {
|
||||
|
||||
let output = AlterTableExprTranslator.translate(&alter_expr).unwrap();
|
||||
assert_eq!("ALTER TABLE test DROP COLUMN foo;", output);
|
||||
|
||||
let alter_expr = AlterTableExpr {
|
||||
table_name: "test".into(),
|
||||
alter_options: AlterTableOperation::ModifyDataType {
|
||||
column: Column {
|
||||
name: "host".into(),
|
||||
column_type: ConcreteDataType::string_datatype(),
|
||||
options: vec![],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let output = AlterTableExprTranslator.translate(&alter_expr).unwrap();
|
||||
assert_eq!("ALTER TABLE test MODIFY COLUMN host STRING;", output);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ impl DslTranslator<AlterTableExpr, String> for AlterTableExprTranslator {
|
||||
AlterTableOperation::RenameTable { new_table_name } => {
|
||||
Self::format_rename(&input.table_name, new_table_name)
|
||||
}
|
||||
AlterTableOperation::ModifyDataType { column } => {
|
||||
Self::format_modify_data_type(&input.table_name, column)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -65,6 +68,13 @@ impl AlterTableExprTranslator {
|
||||
)
|
||||
}
|
||||
|
||||
fn format_modify_data_type(name: impl Display, column: &Column) -> String {
|
||||
format!(
|
||||
"ALTER TABLE {name} MODIFY COLUMN {};",
|
||||
Self::format_column(column)
|
||||
)
|
||||
}
|
||||
|
||||
fn format_column(column: &Column) -> String {
|
||||
vec![
|
||||
column.name.to_string(),
|
||||
@@ -150,5 +160,20 @@ mod tests {
|
||||
|
||||
let output = AlterTableExprTranslator.translate(&alter_expr).unwrap();
|
||||
assert_eq!("ALTER TABLE test DROP COLUMN foo;", output);
|
||||
|
||||
let alter_expr = AlterTableExpr {
|
||||
table_name: "test".into(),
|
||||
alter_options: AlterTableOperation::ModifyDataType {
|
||||
column: Column {
|
||||
name: "host".into(),
|
||||
column_type: ConcreteDataType::string_datatype(),
|
||||
options: vec![],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let output = AlterTableExprTranslator.translate(&alter_expr).unwrap();
|
||||
// Ignores the location and primary key option.
|
||||
assert_eq!("ALTER TABLE test MODIFY COLUMN host STRING;", output);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaChaRng;
|
||||
use snafu::ResultExt;
|
||||
use sqlx::{MySql, Pool};
|
||||
use strum::{EnumIter, IntoEnumIterator};
|
||||
use tests_fuzz::context::{TableContext, TableContextRef};
|
||||
use tests_fuzz::error::{self, Result};
|
||||
use tests_fuzz::fake::{
|
||||
@@ -31,17 +32,16 @@ use tests_fuzz::fake::{
|
||||
};
|
||||
use tests_fuzz::generator::alter_expr::{
|
||||
AlterExprAddColumnGeneratorBuilder, AlterExprDropColumnGeneratorBuilder,
|
||||
AlterExprRenameGeneratorBuilder,
|
||||
AlterExprModifyDataTypeGeneratorBuilder, AlterExprRenameGeneratorBuilder,
|
||||
};
|
||||
use tests_fuzz::generator::create_expr::CreateTableExprGeneratorBuilder;
|
||||
use tests_fuzz::generator::Generator;
|
||||
use tests_fuzz::ir::{droppable_columns, AlterTableExpr, CreateTableExpr};
|
||||
use tests_fuzz::ir::{droppable_columns, modifiable_columns, AlterTableExpr, CreateTableExpr};
|
||||
use tests_fuzz::translator::mysql::alter_expr::AlterTableExprTranslator;
|
||||
use tests_fuzz::translator::mysql::create_expr::CreateTableExprTranslator;
|
||||
use tests_fuzz::translator::DslTranslator;
|
||||
use tests_fuzz::utils::{init_greptime_connections_via_env, Connections};
|
||||
use tests_fuzz::validator;
|
||||
|
||||
struct FuzzContext {
|
||||
greptime: Pool<MySql>,
|
||||
}
|
||||
@@ -58,6 +58,14 @@ struct FuzzInput {
|
||||
actions: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, EnumIter)]
|
||||
enum AlterTableOption {
|
||||
AddColumn,
|
||||
DropColumn,
|
||||
RenameTable,
|
||||
ModifyDataType,
|
||||
}
|
||||
|
||||
fn generate_create_table_expr<R: Rng + 'static>(rng: &mut R) -> Result<CreateTableExpr> {
|
||||
let columns = rng.gen_range(2..30);
|
||||
let create_table_generator = CreateTableExprGeneratorBuilder::default()
|
||||
@@ -76,26 +84,32 @@ fn generate_alter_table_expr<R: Rng + 'static>(
|
||||
table_ctx: TableContextRef,
|
||||
rng: &mut R,
|
||||
) -> Result<AlterTableExpr> {
|
||||
let rename = rng.gen_bool(0.2);
|
||||
if rename {
|
||||
let expr_generator = AlterExprRenameGeneratorBuilder::default()
|
||||
let options = AlterTableOption::iter().collect::<Vec<_>>();
|
||||
match options[rng.gen_range(0..options.len())] {
|
||||
AlterTableOption::DropColumn if !droppable_columns(&table_ctx.columns).is_empty() => {
|
||||
AlterExprDropColumnGeneratorBuilder::default()
|
||||
.table_ctx(table_ctx)
|
||||
.build()
|
||||
.unwrap()
|
||||
.generate(rng)
|
||||
}
|
||||
AlterTableOption::ModifyDataType if !modifiable_columns(&table_ctx.columns).is_empty() => {
|
||||
AlterExprModifyDataTypeGeneratorBuilder::default()
|
||||
.table_ctx(table_ctx)
|
||||
.build()
|
||||
.unwrap()
|
||||
.generate(rng)
|
||||
}
|
||||
AlterTableOption::RenameTable => AlterExprRenameGeneratorBuilder::default()
|
||||
.table_ctx(table_ctx)
|
||||
.name_generator(Box::new(MappedGenerator::new(
|
||||
WordGenerator,
|
||||
merge_two_word_map_fn(random_capitalize_map, uppercase_and_keyword_backtick_map),
|
||||
)))
|
||||
.build()
|
||||
.unwrap();
|
||||
expr_generator.generate(rng)
|
||||
} else {
|
||||
let drop_column = rng.gen_bool(0.5) && !droppable_columns(&table_ctx.columns).is_empty();
|
||||
if drop_column {
|
||||
let expr_generator = AlterExprDropColumnGeneratorBuilder::default()
|
||||
.table_ctx(table_ctx)
|
||||
.build()
|
||||
.unwrap();
|
||||
expr_generator.generate(rng)
|
||||
} else {
|
||||
.unwrap()
|
||||
.generate(rng),
|
||||
_ => {
|
||||
let location = rng.gen_bool(0.5);
|
||||
let expr_generator = AlterExprAddColumnGeneratorBuilder::default()
|
||||
.table_ctx(table_ctx)
|
||||
|
||||
Reference in New Issue
Block a user