Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(fuzz): add insert logical table target #3842

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
target: [ "fuzz_create_table", "fuzz_alter_table", "fuzz_create_database", "fuzz_create_logical_table", "fuzz_alter_logical_table", "fuzz_insert" ]
target: [ "fuzz_create_table", "fuzz_alter_table", "fuzz_create_database", "fuzz_create_logical_table", "fuzz_alter_logical_table", "fuzz_insert", "fuzz_insert_logical_table" ]
steps:
- uses: actions/checkout@v4
- uses: arduino/setup-protoc@v3
Expand Down
7 changes: 7 additions & 0 deletions tests-fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ test = false
bench = false
doc = false

[[bin]]
name = "fuzz_insert_logical_table"
path = "targets/fuzz_insert_logical_table.rs"
test = false
bench = false
doc = false

[[bin]]
name = "fuzz_alter_table"
path = "targets/fuzz_alter_table.rs"
Expand Down
9 changes: 4 additions & 5 deletions tests-fuzz/src/generator/insert_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ use crate::ir::{generate_random_value, Ident};
#[builder(pattern = "owned")]
pub struct InsertExprGenerator<R: Rng + 'static> {
table_ctx: TableContextRef,
// Whether to omit all columns, i.e. INSERT INTO table_name VALUES (...)
omit_column_list: bool,
#[builder(default = "1")]
rows: usize,
#[builder(default = "Box::new(WordGenerator)")]
Expand All @@ -44,11 +46,8 @@ impl<R: Rng + 'static> Generator<InsertIntoExpr, R> for InsertExprGenerator<R> {

/// Generates the [InsertIntoExpr].
fn generate(&self, rng: &mut R) -> Result<InsertIntoExpr> {
// Whether to omit all columns, i.e. INSERT INTO table_name VALUES (...)
let omit_column_list = rng.gen_bool(0.2);

let mut values_columns = vec![];
if omit_column_list {
if self.omit_column_list {
// If omit column list, then all columns are required in the values list
values_columns.clone_from(&self.table_ctx.columns);
} else {
Expand Down Expand Up @@ -94,7 +93,7 @@ impl<R: Rng + 'static> Generator<InsertIntoExpr, R> for InsertExprGenerator<R> {

Ok(InsertIntoExpr {
table_name: self.table_ctx.name.to_string(),
columns: if omit_column_list {
columns: if self.omit_column_list {
vec![]
} else {
values_columns
Expand Down
16 changes: 9 additions & 7 deletions tests-fuzz/src/translator/mysql/insert_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl InsertIntoExprTranslator {
mod tests {
use std::sync::Arc;

use rand::SeedableRng;
use rand::{Rng, SeedableRng};

use super::*;
use crate::generator::insert_expr::InsertExprGeneratorBuilder;
Expand All @@ -82,10 +82,12 @@ mod tests {
#[test]
fn test_insert_into_translator() {
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
let omit_column_list = rng.gen_bool(0.2);

let test_ctx = test_utils::new_test_ctx();
let insert_expr_generator = InsertExprGeneratorBuilder::default()
.table_ctx(Arc::new(test_ctx))
.omit_column_list(omit_column_list)
.rows(2)
.build()
.unwrap();
Expand All @@ -100,16 +102,16 @@ mod tests {

let insert_expr = insert_expr_generator.generate(&mut rng).unwrap();
let output = InsertIntoExprTranslator.translate(&insert_expr).unwrap();
let expected = r#"INSERT INTO test (cpu_util, disk_util, ts) VALUES
(0.7074194466620976, 0.661288102315126, '-47252-05-08 07:33:49.567+0000'),
(0.8266101224213618, 0.7947724277743285, '-224292-12-07 02:51:53.371+0000');"#;
let expected = r#"INSERT INTO test (ts, memory_util) VALUES
('+22606-05-02 04:44:02.976+0000', 0.7074194466620976),
('+33689-06-12 08:42:11.037+0000', 0.40987428386535585);"#;
assert_eq!(output, expected);

let insert_expr = insert_expr_generator.generate(&mut rng).unwrap();
let output = InsertIntoExprTranslator.translate(&insert_expr).unwrap();
let expected = r#"INSERT INTO test VALUES
('odio', NULL, 0.48809950435391647, 0.5228925709595407, 0.9091528874275897, '+241156-12-16 20:52:15.185+0000'),
('dignissimos', 'labore', NULL, 0.12983559048685023, 0.6362040919831425, '-30691-06-17 23:41:09.938+0000');"#;
let expected = r#"INSERT INTO test (ts, disk_util, cpu_util, host) VALUES
('+200107-10-22 01:36:36.924+0000', 0.9082597320638828, 0.020853190804573818, 'voluptates'),
('+241156-12-16 20:52:15.185+0000', 0.6492772846116915, 0.18078027701087784, 'repellat');"#;
assert_eq!(output, expected);
}
}
3 changes: 3 additions & 0 deletions tests-fuzz/targets/fuzz_insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,11 @@ fn generate_insert_expr<R: Rng + 'static>(
rng: &mut R,
table_ctx: TableContextRef,
) -> Result<InsertIntoExpr> {
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();
Expand Down
202 changes: 202 additions & 0 deletions tests-fuzz/targets/fuzz_insert_logical_table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// 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 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::{
CreateLogicalTableExprGeneratorBuilder, CreatePhysicalTableExprGeneratorBuilder,
};
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, Connections};

struct FuzzContext {
greptime: Pool<MySql>,
}

impl FuzzContext {
async fn close(self) {
self.greptime.close().await;
}
}

#[derive(Copy, Clone, Debug)]
struct FuzzInput {
seed: u64,
rows: usize,
}

impl Arbitrary<'_> for FuzzInput {
fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
let seed = u.int_in_range(u64::MIN..=u64::MAX)?;
let mut rng = ChaChaRng::seed_from_u64(seed);
let rows = rng.gen_range(1..4096);
Ok(FuzzInput { rows, seed })
}
}

fn generate_create_physical_table_expr<R: Rng + 'static>(rng: &mut R) -> Result<CreateTableExpr> {
let physical_table_if_not_exists = rng.gen_bool(0.5);
let create_physical_table_expr = CreatePhysicalTableExprGeneratorBuilder::default()
.name_generator(Box::new(MappedGenerator::new(
WordGenerator,
merge_two_word_map_fn(random_capitalize_map, uppercase_and_keyword_backtick_map),
)))
.if_not_exists(physical_table_if_not_exists)
.build()
.unwrap();
create_physical_table_expr.generate(rng)
}

fn generate_create_logical_table_expr<R: Rng + 'static>(
physical_table_ctx: TableContextRef,
rng: &mut R,
) -> Result<CreateTableExpr> {
let labels = rng.gen_range(1..=5);
let logical_table_if_not_exists = rng.gen_bool(0.5);

let create_logical_table_expr = CreateLogicalTableExprGeneratorBuilder::default()
.name_generator(Box::new(MappedGenerator::new(
WordGenerator,
merge_two_word_map_fn(random_capitalize_map, uppercase_and_keyword_backtick_map),
)))
.physical_table_ctx(physical_table_ctx)
.labels(labels)
.if_not_exists(logical_table_if_not_exists)
.build()
.unwrap();
create_logical_table_expr.generate(rng)
}

fn generate_insert_expr<R: Rng + 'static>(
input: FuzzInput,
rng: &mut R,
table_ctx: TableContextRef,
) -> Result<InsertIntoExpr> {
let insert_generator = InsertExprGeneratorBuilder::default()
.omit_column_list(false)
.table_ctx(table_ctx)
.rows(input.rows)
.build()
.unwrap();
insert_generator.generate(rng)
}

async fn execute_insert(ctx: FuzzContext, input: FuzzInput) -> Result<()> {
info!("input: {input:?}");
let mut rng = ChaChaRng::seed_from_u64(input.seed);

// Create a physical table and a logical table on top of it
let create_physical_table_expr = generate_create_physical_table_expr(&mut rng).unwrap();
let translator = CreateTableExprTranslator;
let sql = translator.translate(&create_physical_table_expr)?;
let result = sqlx::query(&sql)
.execute(&ctx.greptime)
.await
.context(error::ExecuteQuerySnafu { sql: &sql })?;
info!("Create physical table: {sql}, result: {result:?}");

let physical_table_ctx = Arc::new(TableContext::from(&create_physical_table_expr));

let create_logical_table_expr =
generate_create_logical_table_expr(physical_table_ctx, &mut rng).unwrap();
let sql = translator.translate(&create_logical_table_expr)?;
let result = sqlx::query(&sql)
.execute(&ctx.greptime)
.await
.context(error::ExecuteQuerySnafu { sql: &sql })?;
info!("Create logical table: {sql}, result: {result:?}");

let logical_table_ctx = Arc::new(TableContext::from(&create_logical_table_expr));

let insert_expr = generate_insert_expr(input, &mut rng, logical_table_ctx)?;
let translator = InsertIntoExprTranslator;
let sql = translator.translate(&insert_expr)?;
let result = ctx
.greptime
// unprepared query, see <https://github.com/GreptimeTeam/greptimedb/issues/3500>
.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(),
)
}
);

// TODO: Validate inserted rows

// Clean up logical table
let sql = format!("DROP TABLE {}", create_logical_table_expr.table_name);
let result = sqlx::query(&sql)
.execute(&ctx.greptime)
.await
.context(error::ExecuteQuerySnafu { sql: &sql })?;
info!(
"Drop table: {}, result: {result:?}",
create_logical_table_expr.table_name
);

// Clean up physical table
let sql = format!("DROP TABLE {}", create_physical_table_expr.table_name);
let result = sqlx::query(&sql)
.execute(&ctx.greptime)
.await
.context(error::ExecuteQuerySnafu { sql })?;
info!(
"Drop table: {}, result: {result:?}",
create_physical_table_expr.table_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_insert(ctx, input)
.await
.unwrap_or_else(|err| panic!("fuzz test must be succeed: {err:?}"));
})
});
Loading