Skip to content

Commit

Permalink
fix: compatibility with mysql types
Browse files Browse the repository at this point in the history
  • Loading branch information
CookiePieWw committed May 14, 2024
1 parent e61b34b commit b8640d1
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 9 deletions.
6 changes: 5 additions & 1 deletion tests-fuzz/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ pub mod select_expr;
use std::fmt;

use datatypes::data_type::ConcreteDataType;
use datatypes::value::Value;
use rand::Rng;

use crate::error::Error;
use crate::ir::create_expr::ColumnOption;
use crate::ir::{AlterTableExpr, CreateTableExpr};
use crate::ir::{AlterTableExpr, CreateTableExpr, Ident};

pub type CreateTableExprGenerator<R> =
Box<dyn Generator<CreateTableExpr, R, Error = Error> + Sync + Send>;
Expand All @@ -36,6 +37,9 @@ pub type ColumnOptionGenerator<R> = Box<dyn Fn(&mut R, &ConcreteDataType) -> Vec

pub type ConcreteDataTypeGenerator<R> = Box<dyn Random<ConcreteDataType, R>>;

pub type ValueGenerator<R> =
Box<dyn Fn(&mut R, &ConcreteDataType, Option<&dyn Random<Ident, R>>) -> Value>;

pub trait Generator<T, R: Rng> {
type Error: Sync + Send + fmt::Debug;

Expand Down
6 changes: 4 additions & 2 deletions tests-fuzz/src/generator/insert_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use rand::Rng;
use crate::context::TableContextRef;
use crate::error::{Error, Result};
use crate::fake::WordGenerator;
use crate::generator::{Generator, Random};
use crate::generator::{Generator, Random, ValueGenerator};
use crate::ir::insert_expr::{InsertIntoExpr, RowValue};
use crate::ir::{generate_random_value, Ident};

Expand All @@ -37,6 +37,8 @@ pub struct InsertExprGenerator<R: Rng + 'static> {
rows: usize,
#[builder(default = "Box::new(WordGenerator)")]
word_generator: Box<dyn Random<Ident, R>>,
#[builder(default = "Box::new(generate_random_value)")]
value_generator: ValueGenerator<R>,
#[builder(default)]
_phantom: PhantomData<R>,
}
Expand Down Expand Up @@ -81,7 +83,7 @@ impl<R: Rng + 'static> Generator<InsertIntoExpr, R> for InsertExprGenerator<R> {
continue;
}

row.push(RowValue::Value(generate_random_value(
row.push(RowValue::Value((self.value_generator)(
rng,
&column.column_type,
Some(self.word_generator.as_ref()),
Expand Down
76 changes: 76 additions & 0 deletions tests-fuzz/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,21 @@ lazy_static! {
];
pub static ref STRING_DATA_TYPES: Vec<ConcreteDataType> =
vec![ConcreteDataType::string_datatype()];
pub static ref MYSQL_TS_DATA_TYPES: Vec<ConcreteDataType> = vec![
// MySQL only permits fractional seconds with up to microseconds (6 digits) precision.
ConcreteDataType::timestamp_microsecond_datatype(),
ConcreteDataType::timestamp_millisecond_datatype(),
ConcreteDataType::timestamp_second_datatype(),
];
}

impl_random!(ConcreteDataType, ColumnTypeGenerator, DATA_TYPES);
impl_random!(ConcreteDataType, TsColumnTypeGenerator, TS_DATA_TYPES);
impl_random!(
ConcreteDataType,
MySQLTsColumnTypeGenerator,
MYSQL_TS_DATA_TYPES
);
impl_random!(
ConcreteDataType,
PartibleColumnTypeGenerator,
Expand All @@ -82,6 +93,7 @@ impl_random!(

pub struct ColumnTypeGenerator;
pub struct TsColumnTypeGenerator;
pub struct MySQLTsColumnTypeGenerator;
pub struct PartibleColumnTypeGenerator;
pub struct StringColumnTypeGenerator;

Expand Down Expand Up @@ -110,6 +122,31 @@ pub fn generate_random_value<R: Rng>(
}
}

/// Generates a random [Value] for MySQL.
pub fn generate_random_value_for_mysql<R: Rng>(
rng: &mut R,
datatype: &ConcreteDataType,
random_str: Option<&dyn Random<Ident, R>>,
) -> Value {
match datatype {
&ConcreteDataType::Boolean(_) => Value::from(rng.gen::<bool>()),
ConcreteDataType::Int16(_) => Value::from(rng.gen::<i16>()),
ConcreteDataType::Int32(_) => Value::from(rng.gen::<i32>()),
ConcreteDataType::Int64(_) => Value::from(rng.gen::<i64>()),
ConcreteDataType::Float32(_) => Value::from(rng.gen::<f32>()),
ConcreteDataType::Float64(_) => Value::from(rng.gen::<f64>()),
ConcreteDataType::String(_) => match random_str {
Some(random) => Value::from(random.gen(rng).value),
None => Value::from(rng.gen::<char>().to_string()),
},
ConcreteDataType::Date(_) => generate_random_date(rng),
ConcreteDataType::DateTime(_) => generate_random_datetime(rng),
&ConcreteDataType::Timestamp(ts_type) => generate_random_timestamp_for_mysql(rng, ts_type),

_ => unimplemented!("unsupported type: {datatype}"),
}
}

fn generate_random_timestamp<R: Rng>(rng: &mut R, ts_type: TimestampType) -> Value {
let v = match ts_type {
TimestampType::Second(_) => {
Expand Down Expand Up @@ -140,6 +177,37 @@ fn generate_random_timestamp<R: Rng>(rng: &mut R, ts_type: TimestampType) -> Val
Value::from(v)
}

// MySQL supports timestamp from '1970-01-01 00:00:01.000000' to '2038-01-19 03:14:07.499999'
fn generate_random_timestamp_for_mysql<R: Rng>(rng: &mut R, ts_type: TimestampType) -> Value {
let v = match ts_type {
TimestampType::Second(_) => {
let min = 1;
let max = 2_147_483_647;
let value = rng.gen_range(min..=max);
Timestamp::new_second(value)
}
TimestampType::Millisecond(_) => {
let min = 1000;
let max = 2_147_483_647_499;
let value = rng.gen_range(min..=max);
Timestamp::new_millisecond(value)
}
TimestampType::Microsecond(_) => {
let min = 1_000_000;
let max = 2_147_483_647_499_999;
let value = rng.gen_range(min..=max);
Timestamp::new_microsecond(value)
}
TimestampType::Nanosecond(_) => {
let min = 1_000_000_000;
let max = 2_147_483_647_499_999_000;
let value = rng.gen_range(min..=max);
Timestamp::new_nanosecond(value)
}
};
Value::from(v)
}

fn generate_random_datetime<R: Rng>(rng: &mut R) -> Value {
let min = i64::from(Timestamp::MIN_MILLISECOND);
let max = i64::from(Timestamp::MAX_MILLISECOND);
Expand Down Expand Up @@ -258,6 +326,14 @@ impl Column {
)
})
}

// Returns default value if it has.
pub fn default_value(&self) -> Option<&Value> {
self.options.iter().find_map(|opt| match opt {
ColumnOption::DefaultValue(value) => Some(value),
_ => None,
})
}
}

/// Returns droppable columns. i.e., non-primary key columns, non-ts columns.
Expand Down
2 changes: 1 addition & 1 deletion tests-fuzz/src/ir/insert_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub struct InsertIntoExpr {

pub type RowValues = Vec<RowValue>;

#[derive(PartialEq, PartialOrd)]
#[derive(PartialEq, PartialOrd, Clone)]
pub enum RowValue {
Value(Value),
Default,
Expand Down
13 changes: 11 additions & 2 deletions tests-fuzz/src/validator/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use crate::ir::insert_expr::{RowValue, RowValues};

/// Asserts fetched_rows are equal to rows
pub fn assert_eq<'a, DB>(
columns: &[crate::ir::Column],
fetched_rows: &'a [<DB as Database>::Row],
rows: &[RowValues],
) -> Result<()>
Expand Down Expand Up @@ -115,9 +116,17 @@ where
}
};

// println!("Expected value: {:?}, got: {:?}", value, fetched_value);
let value = match value {
// In MySQL, boolean is stored as TINYINT(1)
RowValue::Value(Value::Boolean(v)) => RowValue::Value(Value::Int8(*v as i8)),
RowValue::Default => match columns[idx].default_value().unwrap().clone() {
Value::Boolean(v) => RowValue::Value(Value::Int8(v as i8)),
default_value => RowValue::Value(default_value),
},
_ => value.clone(),
};
ensure!(
value == &fetched_value,
value == fetched_value,
error::AssertSnafu {
reason: format!("Expected value: {:?}, got: {:?}", value, fetched_value)
}
Expand Down
8 changes: 6 additions & 2 deletions tests-fuzz/targets/fuzz_insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ use tests_fuzz::fake::{
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::ir::{
generate_random_value_for_mysql, CreateTableExpr, InsertIntoExpr, MySQLTsColumnTypeGenerator,
};
use tests_fuzz::translator::mysql::create_expr::CreateTableExprTranslator;
use tests_fuzz::translator::mysql::insert_expr::InsertIntoExprTranslator;
use tests_fuzz::translator::DslTranslator;
Expand Down Expand Up @@ -81,6 +83,7 @@ fn generate_create_expr<R: Rng + 'static>(
)))
.columns(input.columns)
.engine("mito")
.ts_column_type_generator(Box::new(MySQLTsColumnTypeGenerator))
.build()
.unwrap();
create_table_generator.generate(rng)
Expand All @@ -97,6 +100,7 @@ fn generate_insert_expr<R: Rng + 'static>(
.table_ctx(table_ctx)
.omit_column_list(omit_column_list)
.rows(input.rows)
.value_generator(Box::new(generate_random_value_for_mysql))
.build()
.unwrap();
insert_generator.generate(rng)
Expand Down Expand Up @@ -166,7 +170,7 @@ async fn execute_insert(ctx: FuzzContext, input: FuzzInput) -> Result<()> {
.cmp(&b[ts_column_idx_in_insert])
.unwrap()
});
validator::row::assert_eq::<MySql>(&fetched_rows, &expected_rows)?;
validator::row::assert_eq::<MySql>(&insert_expr.columns, &fetched_rows, &expected_rows)?;

// Cleans up
let sql = format!("DROP TABLE {}", create_expr.table_name);
Expand Down
2 changes: 1 addition & 1 deletion tests-fuzz/targets/fuzz_insert_logical_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ async fn execute_insert(ctx: FuzzContext, input: FuzzInput) -> Result<()> {
.cmp(&b[ts_column_idx_in_insert])
.unwrap()
});
validator::row::assert_eq::<MySql>(&fetched_rows, &expected_rows)?;
validator::row::assert_eq::<MySql>(&insert_expr.columns, &fetched_rows, &expected_rows)?;

// Clean up logical table
let sql = format!("DROP TABLE {}", create_logical_table_expr.table_name);
Expand Down

0 comments on commit b8640d1

Please sign in to comment.