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:
taobo
2024-06-04 21:38:57 +08:00
committed by GitHub
parent 98c19ed0fa
commit dd06e107f9
10 changed files with 157 additions and 19 deletions

View File

@@ -45,6 +45,7 @@ sqlx = { version = "0.6", features = [
"postgres",
"chrono",
] }
strum.workspace = true
tinytemplate = "1.2"
tokio = { workspace = true }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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