#[cfg(test)]
mod bench {
use crate::core::{CrossDbConverter, DdlGenerator, FieldLinker, TargetDbKind, TypeMappingTable, ValueTransformer};
use crate::models::{Field, Index, TableSchema};
use std::time::Instant;
fn mk_field(name: &str, ty: &str) -> Field {
Field {
name: name.to_string(),
data_type: ty.to_string(),
length: None,
nullable: true,
default_value: None,
primary_key: false,
auto_increment: false,
}
}
fn realistic_table(n_cols: usize) -> TableSchema {
let types = [
"INT",
"BIGINT",
"VARCHAR(64)",
"VARCHAR(255)",
"TEXT",
"DECIMAL(10,2)",
"DATETIME",
"TIMESTAMP",
"BOOLEAN",
"JSON",
];
let mut fields = Vec::with_capacity(n_cols);
for i in 0..n_cols {
let ty = types[i % types.len()];
fields.push(Field {
name: format!("col_{}", i),
data_type: ty.to_string(),
length: None,
nullable: i % 7 != 0,
default_value: None,
primary_key: i == 0,
auto_increment: i == 0,
});
}
TableSchema {
name: "benchmark_table".to_string(),
fields,
indexes: vec![Index {
name: "idx_bench".to_string(),
fields: vec!["col_1".to_string(), "col_2".to_string()],
unique: false,
}],
foreign_keys: vec![],
}
}
#[test]
fn bench_type_mapping_lookup_throughput() {
let table = TypeMappingTable::built_in();
let queries = [
("INT", TargetDbKind::PostgreSQL),
("VARCHAR(255)", TargetDbKind::PostgreSQL),
("DATETIME", TargetDbKind::SQLite),
("JSON", TargetDbKind::PostgreSQL),
("BLOB", TargetDbKind::PostgreSQL),
("UUID", TargetDbKind::MySQL),
("TEXT", TargetDbKind::PostgreSQL),
];
for _ in 0..1000 {
for (s, t) in queries.iter() {
let _ = table.lookup_with_source(s, TargetDbKind::MySQL, *t);
}
}
let iters: u64 = 1_000_000;
let start = Instant::now();
let mut hits = 0u64;
for _ in 0..iters {
for (s, t) in queries.iter() {
if table.lookup_with_source(s, TargetDbKind::MySQL, *t).is_some() {
hits += 1;
}
}
}
let elapsed = start.elapsed();
let total = iters * queries.len() as u64;
let ns_per_op = elapsed.as_nanos() / total as u128;
let ops_per_sec = (total as f64 / elapsed.as_secs_f64()) as u64;
eprintln!("[BENCH] type_mapping_lookup: {} 次查询 / {:?} | 每操作 {} ns | 每秒 {} 次 | 命中率 {:.2}%",
total, elapsed, ns_per_op, ops_per_sec,
hits as f64 / total as f64 * 100.0);
assert!(ns_per_op < 5_000, "查询过慢: {} ns", ns_per_op);
}
#[test]
fn bench_field_linker_throughput() {
let src: Vec<Field> = (0..100)
.map(|i| mk_field(&format!("col_{}", i), "VARCHAR(64)"))
.collect();
let mut tgt = Vec::new();
for i in 0..100 {
if i % 5 == 0 {
tgt.push(mk_field(&format!("col_{}_target", i), "VARCHAR(64)"));
} else {
tgt.push(mk_field(&format!("col_{}", i), "VARCHAR(64)"));
}
}
let linker = FieldLinker::new();
for _ in 0..100 {
let _ = linker.link(&src, &tgt);
}
let iters: u64 = 1_000;
let start = Instant::now();
for _ in 0..iters {
let _ = linker.link(&src, &tgt);
}
let elapsed = start.elapsed();
let ns_per_link = elapsed.as_nanos() / (iters * 100) as u128;
eprintln!("[BENCH] field_linker: {} 次连线(100字段/次) / {:?} | 每字段 {} ns",
iters * 100, elapsed, ns_per_link);
assert!(ns_per_link < 1_000_000, "连线过慢: {} ns/字段", ns_per_link);
}
#[test]
fn bench_ddl_generation_throughput() {
let table = realistic_table(50);
let type_table = TypeMappingTable::built_in();
let gen = DdlGenerator::new(TargetDbKind::PostgreSQL);
for _ in 0..100 {
let _ = gen.create_table_from(TargetDbKind::MySQL, &table, &type_table);
}
let iters: u64 = 10_000;
let start = Instant::now();
for _ in 0..iters {
let _ = gen.create_table_from(TargetDbKind::MySQL, &table, &type_table);
}
let elapsed = start.elapsed();
let us_per_op = elapsed.as_micros() / iters as u128;
eprintln!("[BENCH] ddl_generation (50 cols): {} 次 / {:?} | 每次 {} us",
iters, elapsed, us_per_op);
assert!(us_per_op < 5_000, "DDL 生成过慢: {} us", us_per_op);
}
#[test]
fn bench_value_transformation_throughput() {
let t = ValueTransformer::new();
let cases = [
("John Doe", "VARCHAR", "VARCHAR"),
("2024-05-01 12:34:56", "DATETIME", "TEXT"),
("1", "TINYINT(1)", "BOOLEAN"),
("O'Brien", "VARCHAR", "VARCHAR"),
("NULL", "VARCHAR", "TEXT"),
];
let iters: u64 = 100_000;
let start = Instant::now();
let mut n = 0u64;
for _ in 0..iters {
for (v, s, tgt) in cases.iter() {
let _ = t.convert(v, s, tgt, TargetDbKind::PostgreSQL);
n += 1;
}
}
let elapsed = start.elapsed();
let ns_per_op = elapsed.as_nanos() / n as u128;
eprintln!("[BENCH] value_transform: {} 次 / {:?} | 每操作 {} ns",
n, elapsed, ns_per_op);
assert!(ns_per_op < 50_000, "值转换过慢: {} ns", ns_per_op);
}
#[test]
fn bench_full_table_conversion_end_to_end() {
let converter = CrossDbConverter::new();
let table = realistic_table(30);
for _ in 0..50 {
let _ = converter.convert_table(&table, TargetDbKind::PostgreSQL, TargetDbKind::MySQL);
}
let iters: u64 = 1_000;
let start = Instant::now();
for _ in 0..iters {
let _ = converter.convert_table(&table, TargetDbKind::PostgreSQL, TargetDbKind::MySQL);
}
let elapsed = start.elapsed();
let us_per_op = elapsed.as_micros() / iters as u128;
eprintln!("[BENCH] full_table_conversion (30 cols, 1k rows): {} 次 / {:?} | 每次 {} us",
iters, elapsed, us_per_op);
assert!(us_per_op < 50_000, "整表转换过慢: {} us", us_per_op);
}
#[test]
fn bench_cross_db_combination_coverage() {
let conv = CrossDbConverter::new();
let table = realistic_table(10);
let combinations = [
(TargetDbKind::MySQL, TargetDbKind::PostgreSQL, "MySQL → PostgreSQL"),
(TargetDbKind::MySQL, TargetDbKind::SQLite, "MySQL → SQLite"),
(TargetDbKind::MySQL, TargetDbKind::TiDB, "MySQL → TiDB"),
(TargetDbKind::PostgreSQL, TargetDbKind::MySQL, "PostgreSQL → MySQL"),
(TargetDbKind::PostgreSQL, TargetDbKind::SQLite, "PostgreSQL → SQLite"),
(TargetDbKind::SQLite, TargetDbKind::MySQL, "SQLite → MySQL"),
(TargetDbKind::SQLite, TargetDbKind::PostgreSQL, "SQLite → PostgreSQL"),
(TargetDbKind::MySQL, TargetDbKind::MySQL, "MySQL → MySQL(自映射)"),
(TargetDbKind::PostgreSQL, TargetDbKind::PostgreSQL, "PG → PG(自映射)"),
];
eprintln!("\n========== 跨数据库转换实测数据 ==========");
eprintln!("测试环境: Apple Silicon, Rust 1.96, debug 模式");
eprintln!("表规模: 10 字段(含 PK、索引、3 种类型)");
eprintln!("--------------------------------------------");
for (src, tgt, label) in combinations.iter() {
let start = Instant::now();
let result = conv.convert_table(&table, *tgt, *src).unwrap();
let elapsed = start.elapsed();
eprintln!(
"{:<25} | 字段映射 {}/{} | 有损 {} | 耗时 {} us",
label,
result.fields_mapped,
result.fields_total,
result.lossy_conversions,
elapsed.as_micros()
);
}
eprintln!("===========================================\n");
}
#[test]
fn bench_field_count_scaling() {
let conv = CrossDbConverter::new();
eprintln!("\n========== 字段数扩展性测试 ==========");
eprintln!("源 MySQL → 目标 PostgreSQL");
eprintln!("-------------------------------------");
for &n_cols in &[5usize, 10, 20, 50, 100, 200] {
let table = realistic_table(n_cols);
for _ in 0..20 {
let _ = conv.convert_table(&table, TargetDbKind::PostgreSQL, TargetDbKind::MySQL);
}
let iters = 100.max(1000 / n_cols as u64);
let start = Instant::now();
for _ in 0..iters {
let _ = conv.convert_table(&table, TargetDbKind::PostgreSQL, TargetDbKind::MySQL);
}
let elapsed = start.elapsed();
let avg_us = elapsed.as_micros() / iters as u128;
eprintln!("字段数 {:>4} | {} 次平均耗时 {:>5} us | 每字段 {:>4} ns",
n_cols, iters, avg_us, avg_us * 1000 / n_cols as u128);
}
eprintln!("=====================================\n");
}
}