use crate::mysql::query_builder::SqlGenerator;
use proptest::prelude::*;
use std::collections::HashMap;
#[test]
fn test_batch_insert_single_record() {
let mut sql_gen = SqlGenerator::new();
let data = vec![serde_json::json!({"name": "Alice", "age": 30})];
sql_gen
.build_insert_batch("users", &data, &HashMap::new())
.unwrap();
let sql = sql_gen.get_sql();
assert!(
sql.starts_with("INSERT INTO users ("),
"SQL 应以 'INSERT INTO users (' 开头,实际: {}",
sql
);
assert!(
sql.contains(") VALUES "),
"SQL 应包含 ') VALUES ',实际: {}",
sql
);
let open_paren_count = sql.matches('(').count();
assert_eq!(
open_paren_count, 2,
"单条记录 SQL 中 '(' 总数应为 2(字段列表 1 + VALUES 子句 1),实际: {}",
open_paren_count
);
assert_eq!(
sql_gen.get_params().len(),
2,
"单条记录参数数量应为 2,实际: {}",
sql_gen.get_params().len()
);
}
#[test]
fn test_batch_insert_multiple_records() {
let mut sql_gen = SqlGenerator::new();
let data = vec![
serde_json::json!({"id": 1, "name": "Alice", "score": 90}),
serde_json::json!({"id": 2, "name": "Bob", "score": 85}),
serde_json::json!({"id": 3, "name": "Carol", "score": 92}),
];
sql_gen
.build_insert_batch("students", &data, &HashMap::new())
.unwrap();
let sql = sql_gen.get_sql();
let record_count = 3;
let field_count = 3;
let open_paren_count = sql.matches('(').count();
assert_eq!(
open_paren_count,
record_count + 1,
"SQL 中 '(' 总数应为 {}(字段列表 1 + VALUES 子句 {}),实际: {}",
record_count + 1,
record_count,
open_paren_count
);
assert_eq!(
sql_gen.get_params().len(),
record_count * field_count,
"参数数量应为 {}({} 条记录 × {} 个字段),实际: {}",
record_count * field_count,
record_count,
field_count,
sql_gen.get_params().len()
);
}
#[test]
fn test_batch_insert_records_separated_by_comma() {
let mut sql_gen = SqlGenerator::new();
let data = vec![
serde_json::json!({"x": 1}),
serde_json::json!({"x": 2}),
serde_json::json!({"x": 3}),
];
sql_gen
.build_insert_batch("t", &data, &HashMap::new())
.unwrap();
let sql = sql_gen.get_sql();
let open_paren_count = sql.matches('(').count();
assert_eq!(
open_paren_count,
4, "3 条记录的 SQL 中 '(' 总数应为 4,实际: {},SQL: {}",
open_paren_count,
sql
);
}
#[test]
fn test_batch_insert_empty_data_returns_error() {
let mut sql_gen = SqlGenerator::new();
let result = sql_gen.build_insert_batch("t", &[], &HashMap::new());
assert!(result.is_err(), "空数据列表应返回错误");
}
#[test]
fn test_batch_insert_placeholder_count_equals_params() {
let mut sql_gen = SqlGenerator::new();
let data = vec![
serde_json::json!({"a": 1, "b": 2, "c": 3}),
serde_json::json!({"a": 4, "b": 5, "c": 6}),
];
sql_gen
.build_insert_batch("tbl", &data, &HashMap::new())
.unwrap();
let sql = sql_gen.get_sql();
let placeholder_count = sql.matches('?').count();
let params_count = sql_gen.get_params().len();
assert_eq!(
placeholder_count, params_count,
"SQL 中 '?' 占位符数量应等于参数数量,占位符: {},参数: {}",
placeholder_count, params_count
);
}
proptest! {
#[test]
fn prop_p1_batch_insert_paren_count_equals_record_count(
raw_field_names in prop::collection::vec("[a-z][a-z0-9_]{0,6}", 1..=6),
record_values in prop::collection::vec(
prop::collection::vec(0i64..=9999i64, 1..=6),
1..=20
),
) {
let mut unique_fields: Vec<String> = Vec::new();
let mut seen = std::collections::HashSet::new();
for name in &raw_field_names {
if seen.insert(name.clone()) {
unique_fields.push(name.clone());
}
}
prop_assume!(!unique_fields.is_empty());
let field_count = unique_fields.len();
let data_list: Vec<serde_json::Value> = record_values
.iter()
.map(|values| {
let mut obj = serde_json::Map::new();
for (i, field) in unique_fields.iter().enumerate() {
let val = values[i % values.len()];
obj.insert(field.clone(), serde_json::json!(val));
}
serde_json::Value::Object(obj)
})
.collect();
let record_count = data_list.len();
let mut sql_gen = SqlGenerator::new();
sql_gen.build_insert_batch("test_table", &data_list, &HashMap::new()).unwrap();
let sql = sql_gen.get_sql().to_string();
let params_count = sql_gen.get_params().len();
prop_assert!(
sql.starts_with("INSERT INTO test_table ("),
"SQL 应以 'INSERT INTO test_table (' 开头,实际: {}",
sql
);
prop_assert!(
sql.contains(") VALUES "),
"SQL 应包含 ') VALUES ',实际: {}",
sql
);
let open_paren_count = sql.matches('(').count();
prop_assert_eq!(
open_paren_count,
record_count + 1,
"SQL 中 '(' 总数应为 {}(字段列表 1 + VALUES 子句 {}),实际: {},SQL: {}",
record_count + 1,
record_count,
open_paren_count,
sql
);
prop_assert_eq!(
params_count,
record_count * field_count,
"参数数量应为 {}({} 条记录 × {} 个字段),实际: {}",
record_count * field_count,
record_count,
field_count,
params_count
);
let placeholder_count = sql.matches('?').count();
prop_assert_eq!(
placeholder_count,
params_count,
"SQL 中 '?' 占位符数量应等于参数数量,占位符: {},参数: {}",
placeholder_count,
params_count
);
}
#[test]
fn prop_p1_batch_insert_parens_are_balanced(
raw_field_names in prop::collection::vec("[a-z][a-z0-9_]{0,6}", 1..=5),
record_count in 1usize..=15,
) {
let mut unique_fields: Vec<String> = Vec::new();
let mut seen = std::collections::HashSet::new();
for name in &raw_field_names {
if seen.insert(name.clone()) {
unique_fields.push(name.clone());
}
}
prop_assume!(!unique_fields.is_empty());
let data_list: Vec<serde_json::Value> = (0..record_count)
.map(|i| {
let mut obj = serde_json::Map::new();
for field in &unique_fields {
obj.insert(field.clone(), serde_json::json!(i as i64));
}
serde_json::Value::Object(obj)
})
.collect();
let mut sql_gen = SqlGenerator::new();
sql_gen.build_insert_batch("tbl", &data_list, &HashMap::new()).unwrap();
let sql = sql_gen.get_sql();
let open_count = sql.matches('(').count();
let close_count = sql.matches(')').count();
prop_assert_eq!(
open_count,
close_count,
"SQL 中 '(' 数量应等于 ')' 数量,'(': {},')': {},SQL: {}",
open_count,
close_count,
sql
);
prop_assert_eq!(
open_count,
record_count + 1,
"SQL 中 '(' 总数应为 {},实际: {}",
record_count + 1,
open_count
);
}
}