mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-01-04 12:22:55 +00:00
fix(fuzz): sort inserted rows with primary keys and time index (#4008)
* fix(fuzz): sort inserted rows with primary keys and time index * fix: correct index when replacing default * fix: put null behind all values
This commit is contained in:
@@ -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<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::*;
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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::<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()
|
||||
@@ -159,16 +172,29 @@ async fn execute_insert(ctx: FuzzContext, input: FuzzInput) -> Result<()> {
|
||||
.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)?;
|
||||
|
||||
|
||||
@@ -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::<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()
|
||||
@@ -183,16 +195,33 @@ async fn execute_insert(ctx: FuzzContext, input: FuzzInput) -> Result<()> {
|
||||
.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)?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user