From b2f130a70c508ad28a194e5e751050b0c5624793 Mon Sep 17 00:00:00 2001 From: hanxuanliang Date: Sat, 4 May 2024 22:54:44 +0800 Subject: [PATCH 1/4] feat(fuzz): add delete fuzz part --- tests-fuzz/src/generator.rs | 1 + tests-fuzz/src/generator/delete_expr.rs | 51 +++++++++++++ tests-fuzz/src/ir.rs | 1 + tests-fuzz/src/ir/delete_expr.rs | 28 ++++++++ tests-fuzz/src/translator/mysql.rs | 1 + .../src/translator/mysql/delete_expr.rs | 72 +++++++++++++++++++ 6 files changed, 154 insertions(+) create mode 100644 tests-fuzz/src/generator/delete_expr.rs create mode 100644 tests-fuzz/src/ir/delete_expr.rs create mode 100644 tests-fuzz/src/translator/mysql/delete_expr.rs diff --git a/tests-fuzz/src/generator.rs b/tests-fuzz/src/generator.rs index 2f9de0770c98..d1b52ed26d26 100644 --- a/tests-fuzz/src/generator.rs +++ b/tests-fuzz/src/generator.rs @@ -16,6 +16,7 @@ pub mod alter_expr; pub mod create_expr; pub mod insert_expr; pub mod select_expr; +pub mod delete_expr; use std::fmt; diff --git a/tests-fuzz/src/generator/delete_expr.rs b/tests-fuzz/src/generator/delete_expr.rs new file mode 100644 index 000000000000..4308dea8f4aa --- /dev/null +++ b/tests-fuzz/src/generator/delete_expr.rs @@ -0,0 +1,51 @@ +use std::marker::PhantomData; + +use derive_builder::Builder; +use rand::seq::SliceRandom; +use rand::Rng; + +use crate::context::TableContextRef; +use crate::error::{Error, Result}; +use crate::generator::Generator; +use crate::ir::delete_expr::{DeleteExpr, WhereExpr}; +use crate::ir::generate_random_value; + +#[derive(Builder)] +#[builder(pattern = "owned")] +pub struct DeleteExprGenerator { + table_ctx: TableContextRef, + #[builder(default)] + _phantom: PhantomData, +} + +impl Generator for DeleteExprGenerator { + type Error = Error; + + fn generate(&self, rng: &mut R) -> Result { + let selection = rng.gen_range(1..self.table_ctx.columns.len()); + let mut selected_columns = self + .table_ctx + .columns + .choose_multiple(rng, selection) + .cloned() + .collect::>(); + selected_columns.shuffle(rng); + + let mut where_clause = Vec::with_capacity(selected_columns.len()); + + for column in selected_columns.iter() { + let value = generate_random_value(rng, &column.column_type, None); + let condition = WhereExpr { + column: column.name.to_string(), + value, + }; + where_clause.push(condition); + } + + Ok(DeleteExpr { + table_name: self.table_ctx.name.to_string(), + columns: selected_columns, + where_clause, + }) + } +} diff --git a/tests-fuzz/src/ir.rs b/tests-fuzz/src/ir.rs index eb6ee105b2c8..21bdd2ddf75c 100644 --- a/tests-fuzz/src/ir.rs +++ b/tests-fuzz/src/ir.rs @@ -18,6 +18,7 @@ pub(crate) mod alter_expr; pub(crate) mod create_expr; pub(crate) mod insert_expr; pub(crate) mod select_expr; +pub(crate) mod delete_expr; use core::fmt; diff --git a/tests-fuzz/src/ir/delete_expr.rs b/tests-fuzz/src/ir/delete_expr.rs new file mode 100644 index 000000000000..8a70bba5c2ac --- /dev/null +++ b/tests-fuzz/src/ir/delete_expr.rs @@ -0,0 +1,28 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use datatypes::value::Value; + +use crate::ir::Column; + +pub struct WhereExpr { + pub column: String, + pub value: Value, +} + +pub struct DeleteExpr { + pub table_name: String, + pub columns: Vec, + pub where_clause: Vec, +} diff --git a/tests-fuzz/src/translator/mysql.rs b/tests-fuzz/src/translator/mysql.rs index 0c2498a3218b..1a8ee9a50a51 100644 --- a/tests-fuzz/src/translator/mysql.rs +++ b/tests-fuzz/src/translator/mysql.rs @@ -16,3 +16,4 @@ pub mod alter_expr; pub mod create_expr; pub mod insert_expr; pub mod select_expr; +pub mod delete_expr; diff --git a/tests-fuzz/src/translator/mysql/delete_expr.rs b/tests-fuzz/src/translator/mysql/delete_expr.rs new file mode 100644 index 000000000000..f187b1bd5630 --- /dev/null +++ b/tests-fuzz/src/translator/mysql/delete_expr.rs @@ -0,0 +1,72 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::error::{Error, Result}; +use crate::ir::delete_expr::DeleteExpr; +use crate::translator::DslTranslator; + +pub struct DeleteExprTranslator; + +impl DslTranslator for DeleteExprTranslator { + type Error = Error; + + fn translate(&self, input: &DeleteExpr) -> Result { + // Generating WHERE clause if exists + let where_clause = if !input.where_clause.is_empty() { + input + .where_clause + .iter() + .map(|where_expr| format!("{} = '{}'", where_expr.column, where_expr.value)) + .collect::>() + .join(" AND ") + } else { + "1".to_string() + }; + + Ok(format!( + "DELETE FROM {} WHERE {};", + input.table_name, where_clause, + )) + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use rand::SeedableRng; + + use super::DeleteExprTranslator; + use crate::generator::delete_expr::DeleteExprGeneratorBuilder; + use crate::generator::Generator; + use crate::test_utils; + use crate::translator::DslTranslator; + + #[test] + fn test_select_expr_translator() { + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0); + + let test_ctx = test_utils::new_test_ctx(); + let delete_expr_generator = DeleteExprGeneratorBuilder::default() + .table_ctx(Arc::new(test_ctx)) + .build() + .unwrap(); + + let delete_expr = delete_expr_generator.generate(&mut rng).unwrap(); + let output = DeleteExprTranslator.translate(&delete_expr).unwrap(); + + let expected_output = "DELETE FROM test WHERE memory_util = '0.4147731206727985' AND ts = '+169626-08-17 05:35:46.714+0000' AND cpu_util = '0.494276426950336' AND disk_util = '0.9011706134313209';"; + assert_eq!(output, expected_output); + } +} From 0e0f2eeb4f2ee1df1266911efd14a5d5f842b046 Mon Sep 17 00:00:00 2001 From: hanxuanliang Date: Sun, 5 May 2024 10:08:54 +0800 Subject: [PATCH 2/4] feat(fuzz): add fuzz_delete bin --- tests-fuzz/Cargo.toml | 7 ++ tests-fuzz/README.md | 5 ++ tests-fuzz/targets/fuzz_delete.rs | 124 ++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 tests-fuzz/targets/fuzz_delete.rs diff --git a/tests-fuzz/Cargo.toml b/tests-fuzz/Cargo.toml index c7e733448988..af6f188a4f3f 100644 --- a/tests-fuzz/Cargo.toml +++ b/tests-fuzz/Cargo.toml @@ -71,6 +71,13 @@ test = false bench = false doc = false +[[bin]] +name = "fuzz_delete" +path = "targets/fuzz_delete.rs" +test = false +bench = false +doc = false + [[bin]] name = "fuzz_insert_logical_table" path = "targets/fuzz_insert_logical_table.rs" diff --git a/tests-fuzz/README.md b/tests-fuzz/README.md index 780107a65002..7b31a09c9475 100644 --- a/tests-fuzz/README.md +++ b/tests-fuzz/README.md @@ -36,6 +36,11 @@ cargo fuzz list --fuzz-dir tests-fuzz cargo fuzz run fuzz_create_table --fuzz-dir tests-fuzz ``` +> Note: if you meet the error, you may need to run : +> ```bash +> cargo fuzz run fuzz_create_table --fuzz-dir tests-fuzz -D -s none +> ``` + ## Crash Reproduction If you want to reproduce a crash, you first need to obtain the Base64 encoded code, which usually appears at the end of a crash report, and store it in a file. diff --git a/tests-fuzz/targets/fuzz_delete.rs b/tests-fuzz/targets/fuzz_delete.rs new file mode 100644 index 000000000000..ed810fe205b4 --- /dev/null +++ b/tests-fuzz/targets/fuzz_delete.rs @@ -0,0 +1,124 @@ +// Copyright 2023 Greptime Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_main] +#![allow(dead_code)] +#![allow(unused_imports)] +#![allow(unused_variables)] + +use std::sync::Arc; + +use common_telemetry::info; +use libfuzzer_sys::arbitrary::{Arbitrary, Unstructured}; +use libfuzzer_sys::fuzz_target; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaChaRng; +use snafu::{ensure, ResultExt}; +use sqlx::{Executor, MySql, Pool}; +use tests_fuzz::context::{TableContext, TableContextRef}; +use tests_fuzz::error::{self, Result}; +use tests_fuzz::fake::{ + merge_two_word_map_fn, random_capitalize_map, uppercase_and_keyword_backtick_map, + MappedGenerator, WordGenerator, +}; +use tests_fuzz::generator::create_expr::CreateTableExprGeneratorBuilder; +use tests_fuzz::generator::insert_expr::InsertExprGeneratorBuilder; +use tests_fuzz::generator::Generator; +use tests_fuzz::ir::{CreateTableExpr, InsertIntoExpr}; +use tests_fuzz::translator::mysql::create_expr::CreateTableExprTranslator; +use tests_fuzz::translator::mysql::insert_expr::InsertIntoExprTranslator; +use tests_fuzz::translator::DslTranslator; +use tests_fuzz::utils::{init_greptime_connections_via_env, Connections}; + +struct FuzzContext { + greptime: Pool, +} + +impl FuzzContext { + async fn close(self) { + self.greptime.close().await; + } +} + +#[derive(Copy, Clone, Debug)] +struct FuzzInput { + seed: u64, + columns: usize, + rows: usize, +} + +impl Arbitrary<'_> for FuzzInput { + fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result { + let seed = u.int_in_range(u64::MIN..=u64::MAX)?; + let mut rng = ChaChaRng::seed_from_u64(seed); + let columns = rng.gen_range(2..30); + let rows = rng.gen_range(1..4096); + Ok(FuzzInput { + columns, + rows, + seed, + }) + } +} + +fn generate_create_expr( + input: FuzzInput, + rng: &mut R, +) -> Result { + let create_table_generator = CreateTableExprGeneratorBuilder::default() + .name_generator(Box::new(MappedGenerator::new( + WordGenerator, + merge_two_word_map_fn(random_capitalize_map, uppercase_and_keyword_backtick_map), + ))) + .columns(input.columns) + .engine("mito") + .build() + .unwrap(); + create_table_generator.generate(rng) +} + +fn generate_insert_expr( + input: FuzzInput, + rng: &mut R, + table_ctx: TableContextRef, +) -> Result { + let omit_column_list = rng.gen_bool(0.2); + + let insert_generator = InsertExprGeneratorBuilder::default() + .table_ctx(table_ctx) + .omit_column_list(omit_column_list) + .rows(input.rows) + .build() + .unwrap(); + insert_generator.generate(rng) +} + +async fn execute_delete(ctx: FuzzContext, input: FuzzInput) -> Result<()> { + info!("input: {input:?}"); + + Ok(()) +} + +fuzz_target!(|input: FuzzInput| { + common_telemetry::init_default_ut_logging(); + common_runtime::block_on_write(async { + let Connections { mysql } = init_greptime_connections_via_env().await; + let ctx = FuzzContext { + greptime: mysql.expect("mysql connection init must be succeed"), + }; + execute_delete(ctx, input) + .await + .unwrap_or_else(|err| panic!("fuzz test must be succeed: {err:?}")); + }) +}); From a511d521835ea92dd100ffed652053c420db8bb8 Mon Sep 17 00:00:00 2001 From: hanxuanliang Date: Sun, 5 May 2024 23:25:27 +0800 Subject: [PATCH 3/4] feat(fuzz): update miss execute_delete --- tests-fuzz/src/ir.rs | 1 + tests-fuzz/targets/fuzz_delete.rs | 81 ++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/tests-fuzz/src/ir.rs b/tests-fuzz/src/ir.rs index 21bdd2ddf75c..168f01539b6a 100644 --- a/tests-fuzz/src/ir.rs +++ b/tests-fuzz/src/ir.rs @@ -30,6 +30,7 @@ use datatypes::types::TimestampType; use datatypes::value::Value; use derive_builder::Builder; pub use insert_expr::InsertIntoExpr; +pub use delete_expr::DeleteExpr; use lazy_static::lazy_static; use rand::seq::SliceRandom; use rand::Rng; diff --git a/tests-fuzz/targets/fuzz_delete.rs b/tests-fuzz/targets/fuzz_delete.rs index ed810fe205b4..d06a2e400d4e 100644 --- a/tests-fuzz/targets/fuzz_delete.rs +++ b/tests-fuzz/targets/fuzz_delete.rs @@ -33,10 +33,12 @@ use tests_fuzz::fake::{ MappedGenerator, WordGenerator, }; use tests_fuzz::generator::create_expr::CreateTableExprGeneratorBuilder; +use tests_fuzz::generator::delete_expr::DeleteExprGeneratorBuilder; use tests_fuzz::generator::insert_expr::InsertExprGeneratorBuilder; use tests_fuzz::generator::Generator; -use tests_fuzz::ir::{CreateTableExpr, InsertIntoExpr}; +use tests_fuzz::ir::{CreateTableExpr, DeleteExpr, InsertIntoExpr}; use tests_fuzz::translator::mysql::create_expr::CreateTableExprTranslator; +use tests_fuzz::translator::mysql::delete_expr::DeleteExprTranslator; use tests_fuzz::translator::mysql::insert_expr::InsertIntoExprTranslator; use tests_fuzz::translator::DslTranslator; use tests_fuzz::utils::{init_greptime_connections_via_env, Connections}; @@ -104,8 +106,85 @@ fn generate_insert_expr( insert_generator.generate(rng) } +fn generate_delete_expr( + input: FuzzInput, + rng: &mut R, + table_ctx: TableContextRef, +) -> Result { + let delete_generator = DeleteExprGeneratorBuilder::default() + .table_ctx(table_ctx) + .build() + .unwrap(); + delete_generator.generate(rng) +} + async fn execute_delete(ctx: FuzzContext, input: FuzzInput) -> Result<()> { info!("input: {input:?}"); + let mut rng = ChaChaRng::seed_from_u64(input.seed); + + let create_expr = generate_create_expr(input, &mut rng)?; + let translator = CreateTableExprTranslator; + let sql = translator.translate(&create_expr)?; + let _result = sqlx::query(&sql) + .execute(&ctx.greptime) + .await + .context(error::ExecuteQuerySnafu { sql: &sql })?; + + // Generate insert expr + let table_ctx = Arc::new(TableContext::from(&create_expr)); + let insert_expr = generate_insert_expr(input, &mut rng, table_ctx)?; + let translator = InsertIntoExprTranslator; + let sql = translator.translate(&insert_expr)?; + let result = ctx + .greptime + .execute(sql.as_str()) + .await + .context(error::ExecuteQuerySnafu { sql: &sql })?; + + ensure!( + result.rows_affected() == input.rows as u64, + error::AssertSnafu { + reason: format!( + "expected rows affected: {}, actual: {}", + input.rows, + result.rows_affected(), + ) + } + ); + + // Generate delete expr + let table_ctx = Arc::new(TableContext::from(&create_expr)); + let delete_expr = generate_delete_expr(input, &mut rng, table_ctx)?; + let translator = DeleteExprTranslator; + let sql = translator.translate(&delete_expr)?; + let result = ctx + .greptime + .execute(sql.as_str()) + .await + .context(error::ExecuteQuerySnafu { sql: &sql })?; + + ensure!( + result.rows_affected() == input.rows as u64, + error::AssertSnafu { + reason: format!( + "expected rows affected: {}, actual: {}", + input.rows, + result.rows_affected(), + ) + } + ); + + // Cleans up + let sql = format!("DROP TABLE {}", create_expr.table_name); + let result = sqlx::query(&sql) + .execute(&ctx.greptime) + .await + .context(error::ExecuteQuerySnafu { sql })?; + info!( + "Drop table: {}\n\nResult: {result:?}\n\n", + create_expr.table_name + ); + ctx.close().await; Ok(()) } From f1d61d78371a39f4d1e231f1af0483819624a379 Mon Sep 17 00:00:00 2001 From: hanxuanliang Date: Mon, 6 May 2024 23:13:01 +0800 Subject: [PATCH 4/4] feat(fuzz): remove unuse column in delete sql --- tests-fuzz/src/generator/delete_expr.rs | 19 ++++++++++++++++--- tests-fuzz/src/ir/delete_expr.rs | 6 +++--- .../src/translator/mysql/delete_expr.rs | 7 ++++--- tests-fuzz/targets/fuzz_delete.rs | 13 +------------ 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/tests-fuzz/src/generator/delete_expr.rs b/tests-fuzz/src/generator/delete_expr.rs index 4308dea8f4aa..23ff0b618bbc 100644 --- a/tests-fuzz/src/generator/delete_expr.rs +++ b/tests-fuzz/src/generator/delete_expr.rs @@ -9,6 +9,7 @@ use crate::error::{Error, Result}; use crate::generator::Generator; use crate::ir::delete_expr::{DeleteExpr, WhereExpr}; use crate::ir::generate_random_value; +use crate::ir::insert_expr::RowValue; #[derive(Builder)] #[builder(pattern = "owned")] @@ -22,10 +23,22 @@ impl Generator for DeleteExprGenerator { type Error = Error; fn generate(&self, rng: &mut R) -> Result { - let selection = rng.gen_range(1..self.table_ctx.columns.len()); - let mut selected_columns = self + let filter_columns = self .table_ctx .columns + .iter() + .filter(|col| !col.is_nullable() && !col.has_default_value()) + .cloned() + .collect::>(); + if filter_columns.is_empty() { + return Ok(DeleteExpr::default()); + } + let selection = if filter_columns.len() > 1 { + rng.gen_range(1..filter_columns.len()) + } else { + 1 + }; + let mut selected_columns = filter_columns .choose_multiple(rng, selection) .cloned() .collect::>(); @@ -37,7 +50,7 @@ impl Generator for DeleteExprGenerator { let value = generate_random_value(rng, &column.column_type, None); let condition = WhereExpr { column: column.name.to_string(), - value, + value: RowValue::Value(value), }; where_clause.push(condition); } diff --git a/tests-fuzz/src/ir/delete_expr.rs b/tests-fuzz/src/ir/delete_expr.rs index 8a70bba5c2ac..7e5f0af76898 100644 --- a/tests-fuzz/src/ir/delete_expr.rs +++ b/tests-fuzz/src/ir/delete_expr.rs @@ -12,15 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use datatypes::value::Value; - +use super::insert_expr::RowValue; use crate::ir::Column; pub struct WhereExpr { pub column: String, - pub value: Value, + pub value: RowValue, } +#[derive(Default)] pub struct DeleteExpr { pub table_name: String, pub columns: Vec, diff --git a/tests-fuzz/src/translator/mysql/delete_expr.rs b/tests-fuzz/src/translator/mysql/delete_expr.rs index f187b1bd5630..afc3d9fc612d 100644 --- a/tests-fuzz/src/translator/mysql/delete_expr.rs +++ b/tests-fuzz/src/translator/mysql/delete_expr.rs @@ -27,7 +27,7 @@ impl DslTranslator for DeleteExprTranslator { input .where_clause .iter() - .map(|where_expr| format!("{} = '{}'", where_expr.column, where_expr.value)) + .map(|where_expr| format!("{} = {}", where_expr.column, where_expr.value)) .collect::>() .join(" AND ") } else { @@ -54,7 +54,7 @@ mod tests { use crate::translator::DslTranslator; #[test] - fn test_select_expr_translator() { + fn test_delete_expr_translator() { let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0); let test_ctx = test_utils::new_test_ctx(); @@ -65,8 +65,9 @@ mod tests { let delete_expr = delete_expr_generator.generate(&mut rng).unwrap(); let output = DeleteExprTranslator.translate(&delete_expr).unwrap(); + // println!("output: {}", output); - let expected_output = "DELETE FROM test WHERE memory_util = '0.4147731206727985' AND ts = '+169626-08-17 05:35:46.714+0000' AND cpu_util = '0.494276426950336' AND disk_util = '0.9011706134313209';"; + let expected_output = "DELETE FROM test WHERE ts = '+104408-01-06 12:42:54.931+0000'"; assert_eq!(output, expected_output); } } diff --git a/tests-fuzz/targets/fuzz_delete.rs b/tests-fuzz/targets/fuzz_delete.rs index d06a2e400d4e..cdd666277b1d 100644 --- a/tests-fuzz/targets/fuzz_delete.rs +++ b/tests-fuzz/targets/fuzz_delete.rs @@ -65,7 +65,7 @@ impl Arbitrary<'_> for FuzzInput { let seed = u.int_in_range(u64::MIN..=u64::MAX)?; let mut rng = ChaChaRng::seed_from_u64(seed); let columns = rng.gen_range(2..30); - let rows = rng.gen_range(1..4096); + let rows = rng.gen_range(1..5); Ok(FuzzInput { columns, rows, @@ -163,17 +163,6 @@ async fn execute_delete(ctx: FuzzContext, input: FuzzInput) -> Result<()> { .await .context(error::ExecuteQuerySnafu { sql: &sql })?; - ensure!( - result.rows_affected() == input.rows as u64, - error::AssertSnafu { - reason: format!( - "expected rows affected: {}, actual: {}", - input.rows, - result.rows_affected(), - ) - } - ); - // Cleans up let sql = format!("DROP TABLE {}", create_expr.table_name); let result = sqlx::query(&sql)