From dd06e107f911130e30c2b9b7b944a8fda4edbf8a Mon Sep 17 00:00:00 2001 From: taobo Date: Tue, 4 Jun 2024 21:38:57 +0800 Subject: [PATCH] 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 --- Cargo.lock | 1 + Makefile | 7 +++ tests-fuzz/Cargo.toml | 1 + tests-fuzz/src/context.rs | 6 +++ tests-fuzz/src/generator/alter_expr.rs | 48 ++++++++++++++++++- tests-fuzz/src/ir.rs | 14 ++++++ tests-fuzz/src/ir/alter_expr.rs | 2 + tests-fuzz/src/translator/mysql/alter_expr.rs | 24 ++++++++++ .../src/translator/postgres/alter_expr.rs | 25 ++++++++++ tests-fuzz/targets/fuzz_alter_table.rs | 48 ++++++++++++------- 10 files changed, 157 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 306bccd24f..95d8379421 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10729,6 +10729,7 @@ dependencies = [ "sql", "sqlparser 0.45.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=54a267ac89c09b11c0c88934690530807185d3e7)", "sqlx", + "strum 0.25.0", "tinytemplate", "tokio", ] diff --git a/Makefile b/Makefile index 6fa1f02f87..85b7740f90 100644 --- a/Makefile +++ b/Makefile @@ -163,6 +163,13 @@ nextest: ## Install nextest tools. sqlness-test: ## Run sqlness test. cargo sqlness +# Run fuzz test ${FUZZ_TARGET}. +RUNS ?= 1 +FUZZ_TARGET ?= fuzz_alter_table +.PHONY: fuzz +fuzz: + cargo fuzz run ${FUZZ_TARGET} --fuzz-dir tests-fuzz -D -s none -- -runs=${RUNS} + .PHONY: check check: ## Cargo check all the targets. cargo check --workspace --all-targets --all-features diff --git a/tests-fuzz/Cargo.toml b/tests-fuzz/Cargo.toml index 22a44e5cb0..cc70f83ee4 100644 --- a/tests-fuzz/Cargo.toml +++ b/tests-fuzz/Cargo.toml @@ -45,6 +45,7 @@ sqlx = { version = "0.6", features = [ "postgres", "chrono", ] } +strum.workspace = true tinytemplate = "1.2" tokio = { workspace = true } diff --git a/tests-fuzz/src/context.rs b/tests-fuzz/src/context.rs index 29536c853c..97d7bf5e2c 100644 --- a/tests-fuzz/src/context.rs +++ b/tests-fuzz/src/context.rs @@ -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) + } } } diff --git a/tests-fuzz/src/generator/alter_expr.rs b/tests-fuzz/src/generator/alter_expr.rs index 1a9d4b965b..03aed702fb 100644 --- a/tests-fuzz/src/generator/alter_expr.rs +++ b/tests-fuzz/src/generator/alter_expr.rs @@ -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( @@ -157,6 +158,39 @@ impl Generator for AlterExprRenameGenerator { } } +/// Generates the [AlterTableOperation::ModifyDataType] of [AlterTableExpr]. +#[derive(Builder)] +#[builder(pattern = "owned")] +pub struct AlterExprModifyDataTypeGenerator { + table_ctx: TableContextRef, + #[builder(default = "Box::new(ColumnTypeGenerator)")] + column_type_generator: ConcreteDataTypeGenerator, +} + +impl Generator for AlterExprModifyDataTypeGenerator { + type Error = Error; + + fn generate(&self, rng: &mut R) -> Result { + 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); } } diff --git a/tests-fuzz/src/ir.rs b/tests-fuzz/src/ir.rs index e88cfc81df..190217858f 100644 --- a/tests-fuzz/src/ir.rs +++ b/tests-fuzz/src/ir.rs @@ -355,6 +355,20 @@ pub fn droppable_columns(columns: &[Column]) -> Vec<&Column> { .collect::>() } +/// 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::>() +} + /// Generates [ColumnOption] for [Column]. pub fn column_options_generator( rng: &mut R, diff --git a/tests-fuzz/src/ir/alter_expr.rs b/tests-fuzz/src/ir/alter_expr.rs index 1738f30cd2..a9fdc18c22 100644 --- a/tests-fuzz/src/ir/alter_expr.rs +++ b/tests-fuzz/src/ir/alter_expr.rs @@ -35,4 +35,6 @@ pub enum AlterTableOperation { DropColumn { name: Ident }, /// `RENAME ` RenameTable { new_table_name: Ident }, + /// `MODIFY COLUMN ` + ModifyDataType { column: Column }, } diff --git a/tests-fuzz/src/translator/mysql/alter_expr.rs b/tests-fuzz/src/translator/mysql/alter_expr.rs index a4edc4b45a..c973d7cb4b 100644 --- a/tests-fuzz/src/translator/mysql/alter_expr.rs +++ b/tests-fuzz/src/translator/mysql/alter_expr.rs @@ -38,6 +38,9 @@ impl DslTranslator 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) -> Option { 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); } } diff --git a/tests-fuzz/src/translator/postgres/alter_expr.rs b/tests-fuzz/src/translator/postgres/alter_expr.rs index ed4c60e6a2..42db202efe 100644 --- a/tests-fuzz/src/translator/postgres/alter_expr.rs +++ b/tests-fuzz/src/translator/postgres/alter_expr.rs @@ -38,6 +38,9 @@ impl DslTranslator 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); } } diff --git a/tests-fuzz/targets/fuzz_alter_table.rs b/tests-fuzz/targets/fuzz_alter_table.rs index a38e9d355a..ced68b22e8 100644 --- a/tests-fuzz/targets/fuzz_alter_table.rs +++ b/tests-fuzz/targets/fuzz_alter_table.rs @@ -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, } @@ -58,6 +58,14 @@ struct FuzzInput { actions: usize, } +#[derive(Debug, EnumIter)] +enum AlterTableOption { + AddColumn, + DropColumn, + RenameTable, + ModifyDataType, +} + fn generate_create_table_expr(rng: &mut R) -> Result { let columns = rng.gen_range(2..30); let create_table_generator = CreateTableExprGeneratorBuilder::default() @@ -76,26 +84,32 @@ fn generate_alter_table_expr( table_ctx: TableContextRef, rng: &mut R, ) -> Result { - let rename = rng.gen_bool(0.2); - if rename { - let expr_generator = AlterExprRenameGeneratorBuilder::default() + let options = AlterTableOption::iter().collect::>(); + 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)