mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-05-30 20:00:36 +00:00
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:
2
.github/workflows/develop.yml
vendored
2
.github/workflows/develop.yml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 },
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
104
tests-fuzz/targets/fuzz_create_database.rs
Normal file
104
tests-fuzz/targets/fuzz_create_database.rs
Normal 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:?}"));
|
||||
})
|
||||
});
|
||||
Reference in New Issue
Block a user