feat(fuzz): add create database target (#3675)

* feat(fuzz): add create database target

* chore(ci): add fuzz_create_database ci cfg
This commit is contained in:
Yohan Wal
2024-04-09 09:33:29 +08:00
committed by GitHub
parent e1d2f9a596
commit 682b04cbe4
8 changed files with 235 additions and 7 deletions

View File

@@ -125,7 +125,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
target: [ "fuzz_create_table", "fuzz_alter_table" ]
target: [ "fuzz_create_table", "fuzz_alter_table", "fuzz_create_database" ]
steps:
- uses: actions/checkout@v4
- uses: arduino/setup-protoc@v3

View File

@@ -63,3 +63,10 @@ path = "targets/fuzz_alter_table.rs"
test = false
bench = false
doc = false
[[bin]]
name = "fuzz_create_database"
path = "targets/fuzz_create_database.rs"
test = false
bench = false
doc = false

View File

@@ -15,7 +15,7 @@
use common_macro::stack_trace_debug;
use snafu::{Location, Snafu};
use crate::ir::create_expr::CreateTableExprBuilderError;
use crate::ir::create_expr::{CreateDatabaseExprBuilderError, CreateTableExprBuilderError};
pub type Result<T> = std::result::Result<T, Error>;
@@ -36,6 +36,13 @@ pub enum Error {
location: Location,
},
#[snafu(display("Failed to build create database expr"))]
BuildCreateDatabaseExpr {
#[snafu(source)]
error: CreateDatabaseExprBuilderError,
location: Location,
},
#[snafu(display("No droppable columns"))]
DroppableColumns { location: Location },

View File

@@ -22,11 +22,11 @@ use super::Generator;
use crate::error::{self, Error, Result};
use crate::fake::{random_capitalize_map, MappedGenerator, WordGenerator};
use crate::generator::{ColumnOptionGenerator, ConcreteDataTypeGenerator, Random};
use crate::ir::create_expr::CreateTableExprBuilder;
use crate::ir::create_expr::{CreateDatabaseExprBuilder, CreateTableExprBuilder};
use crate::ir::{
column_options_generator, generate_columns, generate_random_value,
partible_column_options_generator, ts_column_options_generator, ColumnTypeGenerator,
CreateTableExpr, Ident, PartibleColumnTypeGenerator, TsColumnTypeGenerator,
CreateDatabaseExpr, CreateTableExpr, Ident, PartibleColumnTypeGenerator, TsColumnTypeGenerator,
};
#[derive(Builder)]
@@ -187,6 +187,40 @@ impl<R: Rng + 'static> Generator<CreateTableExpr, R> for CreateTableExprGenerato
}
}
#[derive(Builder)]
#[builder(default, pattern = "owned")]
pub struct CreateDatabaseExprGenerator<R: Rng + 'static> {
#[builder(setter(into))]
database_name: String,
name_generator: Box<dyn Random<Ident, R>>,
if_not_exists: bool,
}
impl<R: Rng + 'static> Default for CreateDatabaseExprGenerator<R> {
fn default() -> Self {
Self {
database_name: String::new(),
name_generator: Box::new(MappedGenerator::new(WordGenerator, random_capitalize_map)),
if_not_exists: false,
}
}
}
impl<R: Rng + 'static> Generator<CreateDatabaseExpr, R> for CreateDatabaseExprGenerator<R> {
type Error = Error;
fn generate(&self, rng: &mut R) -> Result<CreateDatabaseExpr> {
let mut builder = CreateDatabaseExprBuilder::default();
builder.if_not_exists(self.if_not_exists);
if self.database_name.is_empty() {
builder.database_name(self.name_generator.gen(rng));
} else {
builder.database_name(self.database_name.to_string());
}
builder.build().context(error::BuildCreateDatabaseExprSnafu)
}
}
#[cfg(test)]
mod tests {
use datatypes::value::Value;
@@ -248,4 +282,33 @@ mod tests {
let expected = r#"{"table_name":{"value":"tEmporIbUS","quote_style":null},"columns":[{"name":{"value":"IMpEdIT","quote_style":null},"column_type":{"String":null},"options":["PrimaryKey","NotNull"]},{"name":{"value":"natuS","quote_style":null},"column_type":{"Timestamp":{"Nanosecond":null}},"options":["TimeIndex"]},{"name":{"value":"ADIPisCI","quote_style":null},"column_type":{"Int16":{}},"options":[{"DefaultValue":{"Int16":4864}}]},{"name":{"value":"EXpEdita","quote_style":null},"column_type":{"Int64":{}},"options":["PrimaryKey"]},{"name":{"value":"cUlpA","quote_style":null},"column_type":{"Float64":{}},"options":["NotNull"]},{"name":{"value":"MOLeStIAs","quote_style":null},"column_type":{"Boolean":null},"options":["Null"]},{"name":{"value":"cUmquE","quote_style":null},"column_type":{"Float32":{}},"options":[{"DefaultValue":{"Float32":0.21569687}}]},{"name":{"value":"toTAm","quote_style":null},"column_type":{"Float64":{}},"options":["NotNull"]},{"name":{"value":"deBitIs","quote_style":null},"column_type":{"Float32":{}},"options":["Null"]},{"name":{"value":"QUi","quote_style":null},"column_type":{"Int64":{}},"options":["Null"]}],"if_not_exists":true,"partition":{"partition_columns":["IMpEdIT"],"partition_bounds":[{"Value":{"String":"򟘲"}},{"Value":{"String":"򴥫"}},"MaxValue"]},"engine":"mito2","options":{},"primary_keys":[0,3]}"#;
assert_eq!(expected, serialized);
}
#[test]
fn test_create_database_expr_generator() {
let mut rng = rand::thread_rng();
let expr = CreateDatabaseExprGeneratorBuilder::default()
.if_not_exists(true)
.build()
.unwrap()
.generate(&mut rng)
.unwrap();
assert!(expr.if_not_exists);
}
#[test]
fn test_create_database_expr_generator_deterministic() {
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
let expr = CreateDatabaseExprGeneratorBuilder::default()
.if_not_exists(true)
.build()
.unwrap()
.generate(&mut rng)
.unwrap();
let serialized = serde_json::to_string(&expr).unwrap();
let expected =
r#"{"database_name":{"value":"eXPedITa","quote_style":null},"if_not_exists":true}"#;
assert_eq!(expected, serialized);
}
}

View File

@@ -23,7 +23,7 @@ use core::fmt;
pub use alter_expr::AlterTableExpr;
use common_time::{Date, DateTime, Timestamp};
pub use create_expr::CreateTableExpr;
pub use create_expr::{CreateDatabaseExpr, CreateTableExpr};
use datatypes::data_type::ConcreteDataType;
use datatypes::types::TimestampType;
use datatypes::value::Value;

View File

@@ -64,3 +64,11 @@ pub struct CreateTableExpr {
pub options: HashMap<String, Value>,
pub primary_keys: Vec<usize>,
}
#[derive(Debug, Builder, Clone, Serialize, Deserialize)]
pub struct CreateDatabaseExpr {
#[builder(setter(into))]
pub database_name: Ident,
#[builder(default)]
pub if_not_exists: bool,
}

View File

@@ -19,7 +19,7 @@ use sql::statements::concrete_data_type_to_sql_data_type;
use crate::error::{Error, Result};
use crate::ir::create_expr::ColumnOption;
use crate::ir::{Column, CreateTableExpr};
use crate::ir::{Column, CreateDatabaseExpr, CreateTableExpr};
use crate::translator::DslTranslator;
pub struct CreateTableExprTranslator;
@@ -148,13 +148,37 @@ impl CreateTableExprTranslator {
}
}
pub struct CreateDatabaseExprTranslator;
impl DslTranslator<CreateDatabaseExpr, String> for CreateDatabaseExprTranslator {
type Error = Error;
fn translate(&self, input: &CreateDatabaseExpr) -> Result<String> {
Ok(format!(
"CREATE DATABASE{}{};",
Self::create_if_not_exists(input),
input.database_name
))
}
}
impl CreateDatabaseExprTranslator {
fn create_if_not_exists(input: &CreateDatabaseExpr) -> &str {
if input.if_not_exists {
" IF NOT EXISTS "
} else {
" "
}
}
}
#[cfg(test)]
mod tests {
use datatypes::value::Value;
use partition::partition::{PartitionBound, PartitionDef};
use super::CreateTableExprTranslator;
use crate::ir::create_expr::CreateTableExprBuilder;
use crate::ir::create_expr::{CreateDatabaseExprBuilder, CreateTableExprBuilder};
use crate::test_utils;
use crate::translator::DslTranslator;
@@ -200,4 +224,19 @@ PARTITION r2 VALUES LESS THAN (MAXVALUE)
output
);
}
#[test]
fn test_create_database_expr_translator() {
let create_database_expr = CreateDatabaseExprBuilder::default()
.database_name("all_metrics")
.if_not_exists(true)
.build()
.unwrap();
let output = super::CreateDatabaseExprTranslator
.translate(&create_database_expr)
.unwrap();
assert_eq!("CREATE DATABASE IF NOT EXISTS all_metrics;", output);
}
}

View File

@@ -0,0 +1,104 @@
// 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]
use common_telemetry::info;
use libfuzzer_sys::arbitrary::{Arbitrary, Unstructured};
use libfuzzer_sys::fuzz_target;
use rand::SeedableRng;
use rand_chacha::ChaChaRng;
use snafu::ResultExt;
use sqlx::{MySql, Pool};
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::CreateDatabaseExprGeneratorBuilder;
use tests_fuzz::generator::Generator;
use tests_fuzz::ir::CreateDatabaseExpr;
use tests_fuzz::translator::mysql::create_expr::CreateDatabaseExprTranslator;
use tests_fuzz::translator::DslTranslator;
use tests_fuzz::utils::{init_greptime_connections, Connections};
struct FuzzContext {
greptime: Pool<MySql>,
}
impl FuzzContext {
async fn close(self) {
self.greptime.close().await;
}
}
#[derive(Clone, Debug)]
struct FuzzInput {
seed: u64,
}
impl Arbitrary<'_> for FuzzInput {
fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
let seed = u.int_in_range(u64::MIN..=u64::MAX)?;
Ok(FuzzInput { seed })
}
}
fn generate_expr(input: FuzzInput) -> Result<CreateDatabaseExpr> {
let mut rng = ChaChaRng::seed_from_u64(input.seed);
let create_database_generator = CreateDatabaseExprGeneratorBuilder::default()
.name_generator(Box::new(MappedGenerator::new(
WordGenerator,
merge_two_word_map_fn(random_capitalize_map, uppercase_and_keyword_backtick_map),
)))
.build()
.unwrap();
create_database_generator.generate(&mut rng)
}
async fn execute_create_database(ctx: FuzzContext, input: FuzzInput) -> Result<()> {
info!("input: {input:?}");
let expr = generate_expr(input)?;
let translator = CreateDatabaseExprTranslator;
let sql = translator.translate(&expr)?;
let result = sqlx::query(&sql)
.execute(&ctx.greptime)
.await
.context(error::ExecuteQuerySnafu { sql: &sql })?;
info!("Create database: {sql}, result: {result:?}");
// Cleans up
let sql = format!("DROP DATABASE {}", expr.database_name);
let result = sqlx::query(&sql)
.execute(&ctx.greptime)
.await
.context(error::ExecuteQuerySnafu { sql })?;
info!("Drop database: {}, result: {result:?}", expr.database_name);
ctx.close().await;
Ok(())
}
fuzz_target!(|input: FuzzInput| {
common_telemetry::init_default_ut_logging();
common_runtime::block_on_write(async {
let Connections { mysql } = init_greptime_connections().await;
let ctx = FuzzContext {
greptime: mysql.expect("mysql connection init must be succeed"),
};
execute_create_database(ctx, input)
.await
.unwrap_or_else(|err| panic!("fuzz test must be succeed: {err:?}"));
})
});