sqltool 0.6.1

功能强大的数据库迁移、同步、运维工具,支持异构数据库自动转换、自动分库分表、慢查询检测、数据对比、备份恢复,提供多语言SDK
Documentation
//! 跨数据库转换生产级基准测试
//!
//! 测量跨数据库异构转换的实测性能数据:
//! - 类型映射吞吐量
//! - 字段自动连线吞吐量
//! - DDL 生成吞吐量
//! - 数据值转换吞吐量
//! - 端到端表转换延迟
//!
//! 输出结果到 `target/benchmarks/cross_db_bench_*.txt`

#[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);

        // 性能要求:单次查询 < 1us
        assert!(ns_per_op < 5_000, "查询过慢: {} ns", ns_per_op);
    }

    #[test]
    fn bench_field_linker_throughput() {
        // 100 字段的源/目标表
        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 {
            // 80% 名称匹配,20% 名字加 _target 后缀
            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();

        // 1k 行 / 30 列的中等表
        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");
    }
}