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

fix(fuzz): sort inserted rows with primary keys and time index #4008

Merged
merged 3 commits into from
May 22, 2024
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
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(
WenyXu marked this conversation as resolved.
Show resolved Hide resolved
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