diff --git a/tests-fuzz/src/ir.rs b/tests-fuzz/src/ir.rs index 39e9322e4c74..a8907de76d86 100644 --- a/tests-fuzz/src/ir.rs +++ b/tests-fuzz/src/ir.rs @@ -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}; @@ -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; @@ -437,6 +439,42 @@ pub fn generate_columns( .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 { + let index_map: HashMap = 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::*; diff --git a/tests-fuzz/src/ir/insert_expr.rs b/tests-fuzz/src/ir/insert_expr.rs index 1b1c19537675..639f125cbcce 100644 --- a/tests-fuzz/src/ir/insert_expr.rs +++ b/tests-fuzz/src/ir/insert_expr.rs @@ -36,6 +36,8 @@ pub enum RowValue { impl RowValue { pub fn cmp(&self, other: &Self) -> Option { 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), } diff --git a/tests-fuzz/targets/fuzz_insert.rs b/tests-fuzz/targets/fuzz_insert.rs index eab40cb7ec9a..11d02ea63d5e 100644 --- a/tests-fuzz/targets/fuzz_insert.rs +++ b/tests-fuzz/targets/fuzz_insert.rs @@ -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; @@ -141,17 +142,29 @@ 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::>(); + + // 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::>(); + let primary_keys_column_list = primary_keys_idxs_in_insert_expr + .iter() + .map(|&i| insert_expr.columns[i].name.to_string()) + .collect::>() + .join(", ") + .to_string(); + let column_list = insert_expr .columns .iter() @@ -159,16 +172,29 @@ async fn execute_insert(ctx: FuzzContext, input: FuzzInput) -> Result<()> { .collect::>() .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::(&insert_expr.columns, &fetched_rows, &expected_rows)?; diff --git a/tests-fuzz/targets/fuzz_insert_logical_table.rs b/tests-fuzz/targets/fuzz_insert_logical_table.rs index fc8b2f9bd775..0c66bafdc01d 100644 --- a/tests-fuzz/targets/fuzz_insert_logical_table.rs +++ b/tests-fuzz/targets/fuzz_insert_logical_table.rs @@ -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; @@ -163,19 +165,29 @@ 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::>(); + + // 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::>(); + let primary_keys_column_list = primary_keys_idxs_in_insert_expr + .iter() + .map(|&i| insert_expr.columns[i].name.to_string()) + .collect::>() + .join(", ") + .to_string(); + let column_list = insert_expr .columns .iter() @@ -183,16 +195,33 @@ async fn execute_insert(ctx: FuzzContext, input: FuzzInput) -> Result<()> { .collect::>() .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::(&insert_expr.columns, &fetched_rows, &expected_rows)?;