Skip to content

Commit

Permalink
fix(fuzz): sort inserted rows with primary keys and time index (#4008)
Browse files Browse the repository at this point in the history
* fix(fuzz): sort inserted rows with primary keys and time index

* fix: correct index when replacing default

* fix: put null behind all values
  • Loading branch information
CookiePieWw authored May 22, 2024
1 parent b86d79b commit 9800807
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 28 deletions.
38 changes: 38 additions & 0 deletions tests-fuzz/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub(crate) mod insert_expr;
pub(crate) mod select_expr;

use core::fmt;
use std::collections::HashMap;

pub use alter_expr::AlterTableExpr;
use common_time::{Date, DateTime, Timestamp};
Expand All @@ -34,6 +35,7 @@ use rand::seq::SliceRandom;
use rand::Rng;
use serde::{Deserialize, Serialize};

use self::insert_expr::{RowValue, RowValues};
use crate::generator::Random;
use crate::impl_random;
use crate::ir::create_expr::ColumnOption;
Expand Down Expand Up @@ -437,6 +439,42 @@ pub fn generate_columns<R: Rng + 'static>(
.collect()
}

/// Replace Value::Default with the corresponding default value in the rows for comparison.
pub fn replace_default(
rows: &[RowValues],
create_expr: &CreateTableExpr,
insert_expr: &InsertIntoExpr,
) -> Vec<RowValues> {
let index_map: HashMap<usize, usize> = insert_expr
.columns
.iter()
.enumerate()
.map(|(insert_idx, insert_column)| {
let create_idx = create_expr
.columns
.iter()
.position(|create_column| create_column.name == insert_column.name)
.expect("Column not found in create_expr");
(insert_idx, create_idx)
})
.collect();

let mut new_rows = Vec::new();
for row in rows {
let mut new_row = Vec::new();
for (idx, value) in row.iter().enumerate() {
if let RowValue::Default = value {
let column = &create_expr.columns[index_map[&idx]];
new_row.push(RowValue::Value(column.default_value().unwrap().clone()));
} else {
new_row.push(value.clone());
}
}
new_rows.push(new_row);
}
new_rows
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 2 additions & 0 deletions tests-fuzz/src/ir/insert_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub enum RowValue {
impl RowValue {
pub fn cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) {
(RowValue::Value(Value::Null), RowValue::Value(v2)) => v2.partial_cmp(&Value::Null),
(RowValue::Value(v1), RowValue::Value(Value::Null)) => Value::Null.partial_cmp(v1),
(RowValue::Value(v1), RowValue::Value(v2)) => v1.partial_cmp(v2),
_ => panic!("Invalid comparison: {:?} and {:?}", self, other),
}
Expand Down
52 changes: 39 additions & 13 deletions tests-fuzz/targets/fuzz_insert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ use tests_fuzz::generator::create_expr::CreateTableExprGeneratorBuilder;
use tests_fuzz::generator::insert_expr::InsertExprGeneratorBuilder;
use tests_fuzz::generator::Generator;
use tests_fuzz::ir::{
generate_random_value_for_mysql, CreateTableExpr, InsertIntoExpr, MySQLTsColumnTypeGenerator,
generate_random_value_for_mysql, replace_default, CreateTableExpr, InsertIntoExpr,
MySQLTsColumnTypeGenerator,
};
use tests_fuzz::translator::mysql::create_expr::CreateTableExprTranslator;
use tests_fuzz::translator::mysql::insert_expr::InsertIntoExprTranslator;
Expand Down Expand Up @@ -141,34 +142,59 @@ async fn execute_insert(ctx: FuzzContext, input: FuzzInput) -> Result<()> {
);

// Validate inserted rows
let ts_column_idx = create_expr
// The order of inserted rows are random, so we need to sort the inserted rows by primary keys and time index for comparison
let primary_keys_names = create_expr
.columns
.iter()
.position(|c| c.is_time_index())
.unwrap();
let ts_column_name = create_expr.columns[ts_column_idx].name.clone();
let ts_column_idx_in_insert = insert_expr
.filter(|c| c.is_primary_key() || c.is_time_index())
.map(|c| c.name.clone())
.collect::<Vec<_>>();

// Not all primary keys are in insert_expr
let primary_keys_idxs_in_insert_expr = insert_expr
.columns
.iter()
.position(|c| c.name == ts_column_name)
.unwrap();
.enumerate()
.filter(|(_, c)| primary_keys_names.contains(&c.name))
.map(|(i, _)| i)
.collect::<Vec<_>>();
let primary_keys_column_list = primary_keys_idxs_in_insert_expr
.iter()
.map(|&i| insert_expr.columns[i].name.to_string())
.collect::<Vec<_>>()
.join(", ")
.to_string();

let column_list = insert_expr
.columns
.iter()
.map(|c| c.name.to_string())
.collect::<Vec<_>>()
.join(", ")
.to_string();

let select_sql = format!(
"SELECT {} FROM {} ORDER BY {}",
column_list, create_expr.table_name, ts_column_name
column_list, create_expr.table_name, primary_keys_column_list
);
let fetched_rows = validator::row::fetch_values(&ctx.greptime, select_sql.as_str()).await?;
let mut expected_rows = insert_expr.values_list;
let mut expected_rows = replace_default(&insert_expr.values_list, &create_expr, &insert_expr);
expected_rows.sort_by(|a, b| {
a[ts_column_idx_in_insert]
.cmp(&b[ts_column_idx_in_insert])
.unwrap()
let a_keys: Vec<_> = primary_keys_idxs_in_insert_expr
.iter()
.map(|&i| &a[i])
.collect();
let b_keys: Vec<_> = primary_keys_idxs_in_insert_expr
.iter()
.map(|&i| &b[i])
.collect();
for (a_key, b_key) in a_keys.iter().zip(b_keys.iter()) {
match a_key.cmp(b_key) {
Some(std::cmp::Ordering::Equal) => continue,
non_eq => return non_eq.unwrap(),
}
}
std::cmp::Ordering::Equal
});
validator::row::assert_eq::<MySql>(&insert_expr.columns, &fetched_rows, &expected_rows)?;

Expand Down
59 changes: 44 additions & 15 deletions tests-fuzz/targets/fuzz_insert_logical_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ use tests_fuzz::generator::create_expr::{
};
use tests_fuzz::generator::insert_expr::InsertExprGeneratorBuilder;
use tests_fuzz::generator::Generator;
use tests_fuzz::ir::{generate_random_value_for_mysql, CreateTableExpr, InsertIntoExpr};
use tests_fuzz::ir::{
generate_random_value_for_mysql, replace_default, CreateTableExpr, InsertIntoExpr,
};
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 @@ -163,36 +165,63 @@ async fn execute_insert(ctx: FuzzContext, input: FuzzInput) -> Result<()> {
);

// Validate inserted rows
let ts_column_idx = create_logical_table_expr
// The order of inserted rows are random, so we need to sort the inserted rows by primary keys and time index for comparison
let primary_keys_names = create_logical_table_expr
.columns
.iter()
.position(|c| c.is_time_index())
.unwrap();
let ts_column_name = create_logical_table_expr.columns[ts_column_idx]
.name
.clone();
let ts_column_idx_in_insert = insert_expr
.filter(|c| c.is_primary_key() || c.is_time_index())
.map(|c| c.name.clone())
.collect::<Vec<_>>();

// Not all primary keys are in insert_expr
let primary_keys_idxs_in_insert_expr = insert_expr
.columns
.iter()
.position(|c| c.name == ts_column_name)
.unwrap();
.enumerate()
.filter(|(_, c)| primary_keys_names.contains(&c.name))
.map(|(i, _)| i)
.collect::<Vec<_>>();
let primary_keys_column_list = primary_keys_idxs_in_insert_expr
.iter()
.map(|&i| insert_expr.columns[i].name.to_string())
.collect::<Vec<_>>()
.join(", ")
.to_string();

let column_list = insert_expr
.columns
.iter()
.map(|c| c.name.to_string())
.collect::<Vec<_>>()
.join(", ")
.to_string();

let select_sql = format!(
"SELECT {} FROM {} ORDER BY {}",
column_list, create_logical_table_expr.table_name, ts_column_name
column_list, create_logical_table_expr.table_name, primary_keys_column_list
);
let fetched_rows = validator::row::fetch_values(&ctx.greptime, select_sql.as_str()).await?;
let mut expected_rows = insert_expr.values_list;
let mut expected_rows = replace_default(
&insert_expr.values_list,
&create_logical_table_expr,
&insert_expr,
);
expected_rows.sort_by(|a, b| {
a[ts_column_idx_in_insert]
.cmp(&b[ts_column_idx_in_insert])
.unwrap()
let a_keys: Vec<_> = primary_keys_idxs_in_insert_expr
.iter()
.map(|&i| &a[i])
.collect();
let b_keys: Vec<_> = primary_keys_idxs_in_insert_expr
.iter()
.map(|&i| &b[i])
.collect();
for (a_key, b_key) in a_keys.iter().zip(b_keys.iter()) {
match a_key.cmp(b_key) {
Some(std::cmp::Ordering::Equal) => continue,
non_eq => return non_eq.unwrap(),
}
}
std::cmp::Ordering::Equal
});
validator::row::assert_eq::<MySql>(&insert_expr.columns, &fetched_rows, &expected_rows)?;

Expand Down

0 comments on commit 9800807

Please sign in to comment.