Skip to main content

dbnexus/domain/migration/
differ.rs

1// Copyright (c) 2026 Kirky.X
2//
3// Licensed under the MIT License
4// See LICENSE file in the project root for full license information.
5
6//! 迁移差异计算和 SQL 生成
7//!
8//! 计算模式差异并生成迁移 SQL
9
10use super::schema::*;
11use super::types::*;
12use crate::foundation::config::DatabaseType;
13use regex::Regex;
14use std::sync::LazyLock;
15
16/// 验证 SQL 标识符(表名、列名等)
17///
18/// # Arguments
19///
20/// * `identifier` - 要验证的标识符
21/// * `identifier_type` - 标识符类型描述(用于错误信息)
22///
23/// # Returns
24///
25/// 验证通过返回标识符,失败返回错误
26fn validate_sql_identifier(identifier: &str, identifier_type: &str) -> Result<String, String> {
27    if identifier.is_empty() {
28        return Err(format!("{} 不能为空", identifier_type));
29    }
30
31    if identifier.len() > 64 {
32        return Err(format!("{} 长度不能超过 64 个字符", identifier_type));
33    }
34
35    // 验证标识符格式:只允许字母、数字、下划线,且不能以数字开头
36    static IDENTIFIER_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_]*$").unwrap());
37
38    if !IDENTIFIER_REGEX.is_match(identifier) {
39        return Err(format!(
40            "{} '{}' 包含无效字符,只允许字母、数字和下划线,且不能以数字开头",
41            identifier_type, identifier
42        ));
43    }
44
45    // 检查保留关键字
46    let reserved_keywords = [
47        "select",
48        "insert",
49        "update",
50        "delete",
51        "drop",
52        "create",
53        "alter",
54        "table",
55        "index",
56        "from",
57        "where",
58        "and",
59        "or",
60        "not",
61        "null",
62        "primary",
63        "key",
64        "foreign",
65        "references",
66        "constraint",
67        "default",
68        "unique",
69        "check",
70        "into",
71        "values",
72        "set",
73        "join",
74        "left",
75        "right",
76        "inner",
77        "outer",
78    ];
79
80    if reserved_keywords.contains(&identifier.to_lowercase().as_str()) {
81        return Err(format!(
82            "{} '{}' 是 SQL 保留关键字,不允许使用",
83            identifier_type, identifier
84        ));
85    }
86
87    Ok(identifier.to_string())
88}
89
90/// 清理默认值,移除可能的 SQL 注入载荷
91fn sanitize_default_value(default: &str) -> String {
92    // 如果默认值包含可疑模式,返回安全的默认值
93    let suspicious_patterns = [
94        "select",
95        "insert",
96        "update",
97        "delete",
98        "drop",
99        "create",
100        "alter",
101        "exec",
102        "execute",
103        "xp_",
104        "sp_",
105        "--",
106        "/*",
107        "*/",
108        "chr(",
109        "char(",
110        "concat",
111        "union",
112        "benchmark",
113        "sleep",
114    ];
115
116    let lower_default = default.to_lowercase();
117
118    for pattern in &suspicious_patterns {
119        if lower_default.contains(pattern) {
120            return "'***SANITIZED***'".to_string();
121        }
122    }
123
124    // 确保默认值被引号包围
125    let trimmed = default.trim();
126
127    if (trimmed.starts_with('\'') && trimmed.ends_with('\''))
128        || (trimmed.starts_with('(') && trimmed.ends_with(')'))
129        || trimmed.parse::<i128>().is_ok()
130        || trimmed.parse::<f64>().is_ok()
131        || trimmed.to_uppercase() == "NULL"
132        || trimmed.to_uppercase() == "CURRENT_TIMESTAMP"
133        || trimmed.to_uppercase() == "NOW()"
134    {
135        // 已经有引号、是函数、数字、NULL 或时间戳,不需要额外引号
136        trimmed.to_string()
137    } else {
138        // 其他情况添加单引号
139        format!("'{}'", trimmed.replace('\'', "''"))
140    }
141}
142
143/// 迁移计划
144pub struct MigrationPlan {
145    /// 待执行的迁移列表
146    pub migrations: Vec<Migration>,
147    /// 执行方向(向上或向下)
148    pub direction: MigrationDirection,
149}
150
151/// 迁移方向
152#[derive(Debug, Clone)]
153pub enum MigrationDirection {
154    /// 向上迁移(应用新版本)
155    Up,
156    /// 向下迁移(回滚版本)
157    Down,
158}
159
160/// 迁移工具 CLI 命令
161#[derive(Debug, Clone)]
162pub enum MigrationCommand {
163    /// 创建新的迁移文件
164    Create {
165        /// 迁移描述
166        description: String,
167        /// 目录路径
168        directory: String,
169    },
170    /// 应用迁移
171    Up {
172        /// 目标版本号,None 表示应用所有迁移
173        target_version: Option<u32>,
174    },
175    /// 回滚迁移
176    Down {
177        /// 目标版本号,None 表示回滚到初始状态
178        target_version: Option<u32>,
179    },
180    /// 查看迁移状态
181    Status,
182    /// 生成迁移文件
183    Generate {
184        /// 从模式生成迁移
185        from_schema: String,
186        /// 到模式
187        to_schema: String,
188        /// 输出文件
189        output_file: String,
190    },
191}
192
193/// Schema 差异计算器
194pub struct SchemaDiffer {
195    /// 源 Schema
196    old_schema: Schema,
197    /// 目标 Schema
198    new_schema: Schema,
199}
200
201impl SchemaDiffer {
202    /// 创建新的 SchemaDiffer
203    pub fn new(old_schema: Schema, new_schema: Schema) -> Self {
204        Self { old_schema, new_schema }
205    }
206
207    /// 计算差异并生成 Migration
208    pub fn diff(&self) -> Vec<Migration> {
209        let mut migrations = Vec::new();
210        let mut migration = Migration::new(1, "Schema changes".to_string());
211
212        // 检测新增的表
213        for new_table in &self.new_schema.tables {
214            if !self.old_schema.has_table(&new_table.name) {
215                migration.add_table_change(TableChange::CreateTable(new_table.clone()));
216            }
217        }
218
219        // 检测删除的表
220        for old_table in &self.old_schema.tables {
221            if !self.new_schema.has_table(&old_table.name) {
222                migration.add_table_change(TableChange::DropTable {
223                    table_name: old_table.name.clone(),
224                });
225            }
226        }
227
228        // 检测修改的表
229        for new_table in &self.new_schema.tables {
230            if let Some(old_table) = self.old_schema.get_table(&new_table.name) {
231                // 检测列变更
232                let column_changes = self.detect_column_changes(old_table, new_table);
233                let added_columns = self.detect_added_columns(old_table, new_table);
234                let removed_columns = self.detect_removed_columns(old_table, new_table);
235                let added_indexes = self.detect_added_indexes(old_table, new_table);
236                let removed_indexes = self.detect_removed_indexes(old_table, new_table);
237                let added_foreign_keys = self.detect_added_foreign_keys(old_table, new_table);
238                let removed_foreign_keys = self.detect_removed_foreign_keys(old_table, new_table);
239
240                if !column_changes.is_empty()
241                    || !added_columns.is_empty()
242                    || !removed_columns.is_empty()
243                    || !added_indexes.is_empty()
244                    || !removed_indexes.is_empty()
245                    || !added_foreign_keys.is_empty()
246                    || !removed_foreign_keys.is_empty()
247                {
248                    migration.add_table_change(TableChange::AlterTable {
249                        table_name: new_table.name.clone(),
250                        column_changes,
251                        added_columns,
252                        removed_columns,
253                        added_indexes,
254                        removed_indexes,
255                        added_foreign_keys,
256                        removed_foreign_keys,
257                    });
258                }
259            }
260        }
261
262        if !migration.table_changes.is_empty() {
263            migrations.push(migration);
264        }
265
266        migrations
267    }
268
269    /// 检测列变更
270    fn detect_column_changes(&self, old_table: &Table, new_table: &Table) -> Vec<ColumnChange> {
271        let mut changes = Vec::new();
272
273        for new_column in &new_table.columns {
274            if let Some(old_column) = old_table.columns.iter().find(|c| c.name == new_column.name) {
275                // 检测类型变更
276                if old_column.column_type != new_column.column_type {
277                    changes.push(ColumnChange::TypeChanged {
278                        column_name: new_column.name.clone(),
279                        old_type: old_column.column_type.clone(),
280                        new_type: new_column.column_type.clone(),
281                    });
282                }
283
284                // 检测可空性变更
285                if old_column.is_nullable != new_column.is_nullable {
286                    changes.push(ColumnChange::NullabilityChanged {
287                        column_name: new_column.name.clone(),
288                        old_nullable: old_column.is_nullable,
289                        new_nullable: new_column.is_nullable,
290                    });
291                }
292
293                // 检测默认值变更
294                if old_column.default_value != new_column.default_value {
295                    changes.push(ColumnChange::DefaultChanged {
296                        column_name: new_column.name.clone(),
297                        old_default: old_column.default_value.clone(),
298                        new_default: new_column.default_value.clone(),
299                    });
300                }
301            }
302        }
303
304        changes
305    }
306
307    /// 检测新增的列
308    fn detect_added_columns(&self, old_table: &Table, new_table: &Table) -> Vec<Column> {
309        new_table
310            .columns
311            .iter()
312            .filter(|c| !old_table.columns.iter().any(|oc| oc.name == c.name))
313            .cloned()
314            .collect()
315    }
316
317    /// 检测删除的列
318    fn detect_removed_columns(&self, old_table: &Table, new_table: &Table) -> Vec<String> {
319        old_table
320            .columns
321            .iter()
322            .filter(|c| !new_table.columns.iter().any(|nc| nc.name == c.name))
323            .map(|c| c.name.clone())
324            .collect()
325    }
326
327    /// 检测新增的索引
328    fn detect_added_indexes(&self, old_table: &Table, new_table: &Table) -> Vec<Index> {
329        new_table
330            .indexes
331            .iter()
332            .filter(|i| !old_table.indexes.iter().any(|oi| oi.name == i.name))
333            .cloned()
334            .collect()
335    }
336
337    /// 检测删除的索引
338    fn detect_removed_indexes(&self, old_table: &Table, new_table: &Table) -> Vec<String> {
339        old_table
340            .indexes
341            .iter()
342            .filter(|i| !new_table.indexes.iter().any(|ni| ni.name == i.name))
343            .map(|i| i.name.clone())
344            .collect()
345    }
346
347    /// 检测新增的外键
348    fn detect_added_foreign_keys(&self, old_table: &Table, new_table: &Table) -> Vec<ForeignKey> {
349        new_table
350            .foreign_keys
351            .iter()
352            .filter(|fk| !old_table.foreign_keys.iter().any(|ofk| ofk.name == fk.name))
353            .cloned()
354            .collect()
355    }
356
357    /// 检测删除的外键
358    fn detect_removed_foreign_keys(&self, old_table: &Table, new_table: &Table) -> Vec<String> {
359        old_table
360            .foreign_keys
361            .iter()
362            .filter(|fk| !new_table.foreign_keys.iter().any(|nfk| nfk.name == fk.name))
363            .map(|fk| fk.name.clone())
364            .collect()
365    }
366}
367
368/// SQL 生成器
369#[derive(Debug, Clone)]
370pub struct SqlGenerator {
371    /// 数据库类型
372    pub db_type: DatabaseType,
373}
374
375impl SqlGenerator {
376    /// 创建新的 SQLGenerator
377    pub fn new(db_type: DatabaseType) -> Self {
378        Self { db_type }
379    }
380
381    /// 生成列定义的 SQL(仅类型部分,用于测试)
382    pub fn generate_column_def(&self, column_type: &ColumnType) -> String {
383        column_type.to_sql(self.db_type)
384    }
385
386    /// 生成创建表的 SQL
387    pub fn generate_create_table_sql(&self, table: &Table) -> Result<String, String> {
388        // 验证表名
389        let table_name = validate_sql_identifier(&table.name, "表名")?;
390
391        let mut sql = format!("CREATE TABLE {} (\n", table_name);
392
393        let column_defs: Vec<String> = table
394            .columns
395            .iter()
396            .map(|col| self.generate_column_definition(col, &table.primary_key_columns))
397            .collect::<Result<Vec<_>, _>>()?;
398
399        sql.push_str(&column_defs.join(",\n"));
400
401        // 添加表级主键约束
402        // SQLite 的自增主键列已在列定义中包含 PRIMARY KEY AUTOINCREMENT,不应在表级别重复声明
403        let columns_with_inline_pk: std::collections::HashSet<&str> =
404            if matches!(self.db_type, DatabaseType::Sqlite | DatabaseType::DuckDb) {
405                table
406                    .columns
407                    .iter()
408                    .filter(|c| c.is_auto_increment && c.is_primary_key)
409                    .map(|c| c.name.as_str())
410                    .collect()
411            } else {
412                std::collections::HashSet::new()
413            };
414        let table_level_pk: Vec<&String> = table
415            .primary_key_columns
416            .iter()
417            .filter(|col| !columns_with_inline_pk.contains(col.as_str()))
418            .collect();
419        if !table_level_pk.is_empty() {
420            sql.push_str(",\n");
421            let pk_columns: Vec<String> = table_level_pk
422                .iter()
423                .map(|col| validate_sql_identifier(col, "主键列名"))
424                .collect::<Result<Vec<_>, _>>()?;
425            sql.push_str(&format!("    PRIMARY KEY ({})", pk_columns.join(", ")));
426        }
427
428        sql.push_str("\n);");
429
430        // 生成索引
431        for index in &table.indexes {
432            if !index.is_constraint {
433                sql.push_str("\n\n");
434                sql.push_str(&self.generate_create_index_sql(index)?);
435            }
436        }
437
438        // 生成外键
439        for fk in &table.foreign_keys {
440            sql.push_str("\n\n");
441            sql.push_str(&self.generate_add_foreign_key_sql(fk)?);
442        }
443
444        Ok(sql)
445    }
446
447    /// 生成列定义
448    fn generate_column_definition(&self, column: &Column, _pk_columns: &[String]) -> Result<String, String> {
449        // 验证列名
450        let column_name = validate_sql_identifier(&column.name, "列名")?;
451
452        let mut def = format!("    {} {}", column_name, column.column_type.to_sql(self.db_type));
453
454        // 自增列不需要指定
455        if column.is_auto_increment && column.is_primary_key {
456            match self.db_type {
457                DatabaseType::MySql => def.push_str(" AUTO_INCREMENT"),
458                DatabaseType::Sqlite => def.push_str(" PRIMARY KEY AUTOINCREMENT"),
459                DatabaseType::DuckDb => def.push_str(" PRIMARY KEY"),
460                _ => {}
461            }
462        }
463
464        if !column.is_nullable {
465            def.push_str(" NOT NULL");
466        }
467
468        if let Some(default) = &column.default_value {
469            let sanitized_default = sanitize_default_value(default);
470            def.push_str(&format!(" DEFAULT {}", sanitized_default));
471        }
472
473        // 主键列如果有自增,不需要单独 PRIMARY KEY
474        if column.is_primary_key && !column.is_auto_increment {
475            // 主键已在表级别处理
476        }
477
478        Ok(def)
479    }
480
481    /// 生成创建索引的 SQL
482    pub fn generate_create_index_sql(&self, index: &Index) -> Result<String, String> {
483        // 验证索引名
484        let index_name = validate_sql_identifier(&index.name, "索引名")?;
485
486        // 验证表名
487        let table_name = validate_sql_identifier(&index.table_name, "表名")?;
488
489        // 验证列名
490        let validated_columns: Vec<String> = index
491            .columns
492            .iter()
493            .map(|col| validate_sql_identifier(col, "索引列名"))
494            .collect::<Result<Vec<_>, _>>()?;
495
496        let unique = if index.is_unique { "UNIQUE " } else { "" };
497        Ok(format!(
498            "CREATE {}INDEX {} ON {} ({})",
499            unique,
500            index_name,
501            table_name,
502            validated_columns.join(", ")
503        ))
504    }
505
506    /// 生成添加外键的 SQL
507    fn generate_add_foreign_key_sql(&self, fk: &ForeignKey) -> Result<String, String> {
508        // 验证所有标识符
509        let table_name = validate_sql_identifier(&fk.table_name, "外键表名")?;
510        let constraint_name = validate_sql_identifier(&fk.name, "外键约束名")?;
511        let column_name = validate_sql_identifier(&fk.column_name, "外键列名")?;
512        let referenced_table_name = validate_sql_identifier(&fk.referenced_table_name, "外键引用表名")?;
513        let referenced_column_name = validate_sql_identifier(&fk.referenced_column_name, "外键引用列名")?;
514
515        let mut sql = format!(
516            "ALTER TABLE {} ADD CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {}({})",
517            table_name, constraint_name, column_name, referenced_table_name, referenced_column_name
518        );
519
520        if let Some(on_delete) = &fk.on_delete {
521            sql.push_str(&format!(" ON DELETE {}", on_delete));
522        }
523
524        if let Some(on_update) = &fk.on_update {
525            sql.push_str(&format!(" ON UPDATE {}", on_update));
526        }
527
528        sql.push(';');
529        Ok(sql)
530    }
531
532    /// 生成删除表的 SQL
533    pub fn generate_drop_table_sql(&self, table_name: &str) -> Result<String, String> {
534        let validated_name = validate_sql_identifier(table_name, "表名")?;
535        Ok(format!("DROP TABLE {};", validated_name))
536    }
537
538    /// 生成添加列的 SQL
539    pub fn generate_add_column_sql(&self, table_name: &str, column: &Column) -> Result<String, String> {
540        // 验证表名
541        let validated_table_name = validate_sql_identifier(table_name, "表名")?;
542
543        let col_def = self.generate_column_definition(column, &Vec::new())?;
544        Ok(format!(
545            "ALTER TABLE {} ADD {};",
546            validated_table_name,
547            col_def.trim_start_matches("    ")
548        ))
549    }
550
551    /// 生成删除列的 SQL
552    pub fn generate_drop_column_sql(&self, table_name: &str, column_name: &str) -> Result<String, String> {
553        // 验证表名和列名
554        let validated_table_name = validate_sql_identifier(table_name, "表名")?;
555        let validated_column_name = validate_sql_identifier(column_name, "列名")?;
556
557        match self.db_type {
558            DatabaseType::MySql => Ok(format!(
559                "ALTER TABLE {} DROP COLUMN {};",
560                validated_table_name, validated_column_name
561            )),
562            DatabaseType::Postgres => Ok(format!(
563                "ALTER TABLE {} DROP COLUMN {};",
564                validated_table_name, validated_column_name
565            )),
566            DatabaseType::DuckDb => Ok(format!(
567                "ALTER TABLE {} DROP COLUMN {};",
568                validated_table_name, validated_column_name
569            )),
570            DatabaseType::Sqlite => {
571                // SQLite 不支持直接删除列,需要重建表
572                Ok(format!(
573                    "-- SQLite 不支持直接删除列,请手动重建表 {}
574 ALTER TABLE {} DROP COLUMN {};",
575                    validated_table_name, validated_table_name, validated_column_name
576                ))
577            }
578        }
579    }
580
581    /// 生成迁移的完整 SQL
582    pub fn generate_migration_sql(&self, migration: &Migration) -> Result<String, String> {
583        let mut sql = String::new();
584
585        for change in &migration.table_changes {
586            match change {
587                TableChange::CreateTable(table) => {
588                    sql.push_str(&format!("-- 创建表: {}\n", table.name));
589                    sql.push_str(&self.generate_create_table_sql(table)?);
590                    sql.push_str("\n\n");
591                }
592                TableChange::DropTable { table_name } => {
593                    sql.push_str(&format!("-- 删除表: {}\n", table_name));
594                    sql.push_str(&self.generate_drop_table_sql(table_name)?);
595                    sql.push_str("\n\n");
596                }
597                TableChange::AlterTable {
598                    table_name,
599                    added_columns,
600                    removed_columns,
601                    added_indexes,
602                    removed_indexes,
603                    added_foreign_keys,
604                    removed_foreign_keys,
605                    ..
606                } => {
607                    sql.push_str(&format!("-- 修改表: {}\n", table_name));
608
609                    for col in added_columns {
610                        sql.push_str(&format!("-- 添加列: {}\n", col.name));
611                        sql.push_str(&self.generate_add_column_sql(table_name, col)?);
612                        sql.push('\n');
613                    }
614
615                    for col_name in removed_columns {
616                        sql.push_str(&format!("-- 删除列: {}\n", col_name));
617                        sql.push_str(&self.generate_drop_column_sql(table_name, col_name)?);
618                        sql.push('\n');
619                    }
620
621                    for index in added_indexes {
622                        sql.push_str(&format!("-- 添加索引: {}\n", index.name));
623                        sql.push_str(&self.generate_create_index_sql(index)?);
624                        sql.push('\n');
625                    }
626
627                    for index_name in removed_indexes {
628                        sql.push_str(&format!("-- 删除索引: {}\n", index_name));
629                        sql.push_str(&format!("DROP INDEX {};\n", index_name));
630                    }
631
632                    for fk in added_foreign_keys {
633                        sql.push_str(&format!("-- 添加外键: {}\n", fk.name));
634                        sql.push_str(&self.generate_add_foreign_key_sql(fk)?);
635                        sql.push('\n');
636                    }
637
638                    for fk_name in removed_foreign_keys {
639                        sql.push_str(&format!("-- 删除外键: {}\n", fk_name));
640                        sql.push_str(&format!("ALTER TABLE {} DROP CONSTRAINT {};\n", table_name, fk_name));
641                    }
642
643                    sql.push('\n');
644                }
645            }
646        }
647
648        Ok(sql.trim_end().to_string())
649    }
650}
651
652#[cfg(test)]
653mod tests {
654    use super::*;
655
656    /// TEST-U-020: ColumnType SQL 生成测试
657    #[test]
658    fn test_column_type_to_sql() {
659        let pg = SqlGenerator::new(DatabaseType::Postgres);
660        let mysql = SqlGenerator::new(DatabaseType::MySql);
661        let sqlite = SqlGenerator::new(DatabaseType::Sqlite);
662
663        // Integer
664        assert_eq!(pg.generate_column_def(&ColumnType::Integer), "INTEGER");
665        assert_eq!(mysql.generate_column_def(&ColumnType::Integer), "INTEGER");
666        assert_eq!(sqlite.generate_column_def(&ColumnType::Integer), "INTEGER");
667
668        // Boolean
669        assert_eq!(pg.generate_column_def(&ColumnType::Boolean), "BOOLEAN");
670        assert_eq!(mysql.generate_column_def(&ColumnType::Boolean), "BOOLEAN");
671        assert_eq!(sqlite.generate_column_def(&ColumnType::Boolean), "INTEGER");
672    }
673
674    /// TEST-U-021: Schema 差异检测测试
675    #[test]
676    fn test_schema_diff_new_table() {
677        let old_schema = Schema::new(DatabaseType::Postgres);
678        let mut new_schema = Schema::new(DatabaseType::Postgres);
679
680        // old: no tables
681        // new: has users table
682        let users_table = Table {
683            name: "users".to_string(),
684            columns: vec![Column {
685                name: "id".to_string(),
686                column_type: ColumnType::Integer,
687                is_primary_key: true,
688                is_nullable: false,
689                has_default: false,
690                default_value: None,
691                is_auto_increment: true,
692                comment: None,
693            }],
694            primary_key_columns: vec!["id".to_string()],
695            indexes: vec![],
696            foreign_keys: vec![],
697            comment: None,
698        };
699        new_schema.add_table(users_table);
700
701        let differ = SchemaDiffer::new(old_schema, new_schema);
702        let migrations = differ.diff();
703
704        assert_eq!(migrations.len(), 1);
705        assert_eq!(migrations[0].table_changes.len(), 1);
706
707        if let TableChange::CreateTable(table) = &migrations[0].table_changes[0] {
708            assert_eq!(table.name, "users");
709        } else {
710            unreachable!("Expected CreateTable change");
711        }
712    }
713
714    /// TEST-U-022: Schema 差异检测 - 删除表
715    #[test]
716    fn test_schema_diff_drop_table() {
717        let mut old_schema = Schema::new(DatabaseType::Postgres);
718        let new_schema = Schema::new(DatabaseType::Postgres);
719
720        let users_table = Table {
721            name: "users".to_string(),
722            columns: vec![],
723            primary_key_columns: vec![],
724            indexes: vec![],
725            foreign_keys: vec![],
726            comment: None,
727        };
728        old_schema.add_table(users_table);
729        // new_schema is empty
730
731        let differ = SchemaDiffer::new(old_schema, new_schema);
732        let migrations = differ.diff();
733
734        assert_eq!(migrations.len(), 1);
735        assert_eq!(migrations[0].table_changes.len(), 1);
736
737        if let TableChange::DropTable { table_name } = &migrations[0].table_changes[0] {
738            assert_eq!(table_name, "users");
739        } else {
740            unreachable!("Expected DropTable change");
741        }
742    }
743
744    /// TEST-U-023: SQL 生成测试
745    #[test]
746    fn test_sql_generation() {
747        let pg = SqlGenerator::new(DatabaseType::Postgres);
748
749        let table = Table {
750            name: "users".to_string(),
751            columns: vec![
752                Column {
753                    name: "id".to_string(),
754                    column_type: ColumnType::Integer,
755                    is_primary_key: true,
756                    is_nullable: false,
757                    has_default: false,
758                    default_value: None,
759                    is_auto_increment: true,
760                    comment: None,
761                },
762                Column {
763                    name: "name".to_string(),
764                    column_type: ColumnType::String(Some(255)),
765                    is_primary_key: false,
766                    is_nullable: false,
767                    has_default: false,
768                    default_value: None,
769                    is_auto_increment: false,
770                    comment: None,
771                },
772            ],
773            primary_key_columns: vec!["id".to_string()],
774            indexes: vec![],
775            foreign_keys: vec![],
776            comment: None,
777        };
778
779        let sql = pg.generate_create_table_sql(&table).expect("Failed to generate SQL");
780
781        assert!(sql.contains("CREATE TABLE users"));
782        assert!(sql.contains("id INTEGER"));
783        assert!(sql.contains("name VARCHAR(255)"));
784        assert!(sql.contains("NOT NULL"));
785        assert!(sql.contains("PRIMARY KEY (id)"));
786    }
787
788    // ===== validate_sql_identifier 测试 =====
789
790    #[test]
791    fn test_validate_sql_identifier_empty() {
792        let result = validate_sql_identifier("", "表名");
793        assert!(result.is_err());
794        assert!(result.unwrap_err().contains("不能为空"));
795    }
796
797    #[test]
798    fn test_validate_sql_identifier_too_long() {
799        let long_name = "a".repeat(65);
800        let result = validate_sql_identifier(&long_name, "表名");
801        assert!(result.is_err());
802        assert!(result.unwrap_err().contains("长度不能超过 64"));
803    }
804
805    #[test]
806    fn test_validate_sql_identifier_invalid_start_with_digit() {
807        let result = validate_sql_identifier("1table", "表名");
808        assert!(result.is_err());
809        assert!(result.unwrap_err().contains("无效字符"));
810    }
811
812    #[test]
813    fn test_validate_sql_identifier_invalid_chars() {
814        let result = validate_sql_identifier("table-name", "表名");
815        assert!(result.is_err());
816        assert!(result.unwrap_err().contains("无效字符"));
817    }
818
819    #[test]
820    fn test_validate_sql_identifier_reserved_keyword() {
821        for kw in &["select", "INSERT", "Drop", "TABLE"] {
822            let result = validate_sql_identifier(kw, "表名");
823            assert!(result.is_err(), "应该拒绝保留关键字: {}", kw);
824            assert!(result.unwrap_err().contains("保留关键字"));
825        }
826    }
827
828    #[test]
829    fn test_validate_sql_identifier_valid_underscore_start() {
830        let result = validate_sql_identifier("_private_table", "表名");
831        assert!(result.is_ok());
832        assert_eq!(result.unwrap(), "_private_table");
833    }
834
835    #[test]
836    fn test_validate_sql_identifier_valid_max_length() {
837        let name = "a".repeat(64);
838        let result = validate_sql_identifier(&name, "表名");
839        assert!(result.is_ok());
840    }
841
842    // ===== sanitize_default_value 测试 =====
843
844    #[test]
845    fn test_sanitize_default_value_suspicious_select() {
846        let result = sanitize_default_value("SELECT * FROM users");
847        assert_eq!(result, "'***SANITIZED***'");
848    }
849
850    #[test]
851    fn test_sanitize_default_value_suspicious_drop() {
852        let result = sanitize_default_value("DROP TABLE users");
853        assert_eq!(result, "'***SANITIZED***'");
854    }
855
856    #[test]
857    fn test_sanitize_default_value_suspicious_comment() {
858        let result = sanitize_default_value("value--comment");
859        assert_eq!(result, "'***SANITIZED***'");
860    }
861
862    #[test]
863    fn test_sanitize_default_value_null() {
864        let result = sanitize_default_value("NULL");
865        assert_eq!(result, "NULL");
866    }
867
868    #[test]
869    fn test_sanitize_default_value_current_timestamp() {
870        let result = sanitize_default_value("CURRENT_TIMESTAMP");
871        assert_eq!(result, "CURRENT_TIMESTAMP");
872    }
873
874    #[test]
875    fn test_sanitize_default_value_now_function() {
876        let result = sanitize_default_value("NOW()");
877        assert_eq!(result, "NOW()");
878    }
879
880    #[test]
881    fn test_sanitize_default_value_integer() {
882        let result = sanitize_default_value("42");
883        assert_eq!(result, "42");
884    }
885
886    #[test]
887    fn test_sanitize_default_value_float() {
888        let result = sanitize_default_value("3.14");
889        assert_eq!(result, "3.14");
890    }
891
892    #[test]
893    fn test_sanitize_default_value_negative_integer() {
894        let result = sanitize_default_value("-100");
895        assert_eq!(result, "-100");
896    }
897
898    #[test]
899    fn test_sanitize_default_value_quoted_string() {
900        let result = sanitize_default_value("'hello'");
901        assert_eq!(result, "'hello'");
902    }
903
904    #[test]
905    fn test_sanitize_default_value_paren_expression() {
906        let result = sanitize_default_value("(1 + 2)");
907        assert_eq!(result, "(1 + 2)");
908    }
909
910    #[test]
911    fn test_sanitize_default_value_plain_string_needs_quotes() {
912        let result = sanitize_default_value("hello");
913        assert_eq!(result, "'hello'");
914    }
915
916    #[test]
917    fn test_sanitize_default_value_string_with_single_quote() {
918        let result = sanitize_default_value("O'Brien");
919        assert_eq!(result, "'O''Brien'");
920    }
921
922    // ===== SchemaDiffer 测试 =====
923
924    fn make_column(name: &str, col_type: ColumnType, nullable: bool, default: Option<&str>) -> Column {
925        Column {
926            name: name.to_string(),
927            column_type: col_type,
928            is_primary_key: false,
929            is_nullable: nullable,
930            has_default: default.is_some(),
931            default_value: default.map(|s| s.to_string()),
932            is_auto_increment: false,
933            comment: None,
934        }
935    }
936
937    fn make_table(name: &str, columns: Vec<Column>) -> Table {
938        Table {
939            name: name.to_string(),
940            columns,
941            primary_key_columns: vec![],
942            indexes: vec![],
943            foreign_keys: vec![],
944            comment: None,
945        }
946    }
947
948    #[test]
949    fn test_diff_no_changes() {
950        let table = make_table("users", vec![make_column("id", ColumnType::Integer, false, None)]);
951        let mut old_schema = Schema::new(DatabaseType::Postgres);
952        let mut new_schema = Schema::new(DatabaseType::Postgres);
953        old_schema.add_table(table.clone());
954        new_schema.add_table(table);
955
956        let differ = SchemaDiffer::new(old_schema, new_schema);
957        let migrations = differ.diff();
958        assert!(migrations.is_empty());
959    }
960
961    #[test]
962    fn test_diff_column_type_changed() {
963        let old_table = make_table("users", vec![make_column("age", ColumnType::Integer, true, None)]);
964        let new_table = make_table("users", vec![make_column("age", ColumnType::BigInteger, true, None)]);
965        let mut old_schema = Schema::new(DatabaseType::Postgres);
966        let mut new_schema = Schema::new(DatabaseType::Postgres);
967        old_schema.add_table(old_table);
968        new_schema.add_table(new_table);
969
970        let differ = SchemaDiffer::new(old_schema, new_schema);
971        let migrations = differ.diff();
972        assert_eq!(migrations.len(), 1);
973        if let TableChange::AlterTable { column_changes, .. } = &migrations[0].table_changes[0] {
974            assert!(
975                column_changes
976                    .iter()
977                    .any(|c| matches!(c, ColumnChange::TypeChanged { .. }))
978            );
979        } else {
980            unreachable!("Expected AlterTable");
981        }
982    }
983
984    #[test]
985    fn test_diff_column_nullability_changed() {
986        let old_table = make_table("users", vec![make_column("name", ColumnType::String(None), true, None)]);
987        let new_table = make_table(
988            "users",
989            vec![make_column("name", ColumnType::String(None), false, None)],
990        );
991        let mut old_schema = Schema::new(DatabaseType::Postgres);
992        let mut new_schema = Schema::new(DatabaseType::Postgres);
993        old_schema.add_table(old_table);
994        new_schema.add_table(new_table);
995
996        let differ = SchemaDiffer::new(old_schema, new_schema);
997        let migrations = differ.diff();
998        assert_eq!(migrations.len(), 1);
999        if let TableChange::AlterTable { column_changes, .. } = &migrations[0].table_changes[0] {
1000            assert!(column_changes.iter().any(|c| matches!(
1001                c,
1002                ColumnChange::NullabilityChanged {
1003                    new_nullable: false,
1004                    ..
1005                }
1006            )));
1007        } else {
1008            unreachable!("Expected AlterTable");
1009        }
1010    }
1011
1012    #[test]
1013    fn test_diff_column_default_changed() {
1014        let old_table = make_table(
1015            "users",
1016            vec![make_column("status", ColumnType::Integer, false, Some("0"))],
1017        );
1018        let new_table = make_table(
1019            "users",
1020            vec![make_column("status", ColumnType::Integer, false, Some("1"))],
1021        );
1022        let mut old_schema = Schema::new(DatabaseType::Postgres);
1023        let mut new_schema = Schema::new(DatabaseType::Postgres);
1024        old_schema.add_table(old_table);
1025        new_schema.add_table(new_table);
1026
1027        let differ = SchemaDiffer::new(old_schema, new_schema);
1028        let migrations = differ.diff();
1029        assert_eq!(migrations.len(), 1);
1030        if let TableChange::AlterTable { column_changes, .. } = &migrations[0].table_changes[0] {
1031            assert!(
1032                column_changes
1033                    .iter()
1034                    .any(|c| matches!(c, ColumnChange::DefaultChanged { .. }))
1035            );
1036        } else {
1037            unreachable!("Expected AlterTable");
1038        }
1039    }
1040
1041    #[test]
1042    fn test_diff_added_and_removed_columns() {
1043        let old_table = make_table(
1044            "users",
1045            vec![
1046                make_column("id", ColumnType::Integer, false, None),
1047                make_column("old_col", ColumnType::Text, true, None),
1048            ],
1049        );
1050        let new_table = make_table(
1051            "users",
1052            vec![
1053                make_column("id", ColumnType::Integer, false, None),
1054                make_column("new_col", ColumnType::Text, true, None),
1055            ],
1056        );
1057        let mut old_schema = Schema::new(DatabaseType::Postgres);
1058        let mut new_schema = Schema::new(DatabaseType::Postgres);
1059        old_schema.add_table(old_table);
1060        new_schema.add_table(new_table);
1061
1062        let differ = SchemaDiffer::new(old_schema, new_schema);
1063        let migrations = differ.diff();
1064        assert_eq!(migrations.len(), 1);
1065        if let TableChange::AlterTable {
1066            added_columns,
1067            removed_columns,
1068            ..
1069        } = &migrations[0].table_changes[0]
1070        {
1071            assert_eq!(added_columns.len(), 1);
1072            assert_eq!(added_columns[0].name, "new_col");
1073            assert_eq!(removed_columns.len(), 1);
1074            assert_eq!(removed_columns[0], "old_col");
1075        } else {
1076            unreachable!("Expected AlterTable");
1077        }
1078    }
1079
1080    #[test]
1081    fn test_diff_added_and_removed_indexes() {
1082        let old_index = Index {
1083            name: "idx_old".to_string(),
1084            table_name: "users".to_string(),
1085            columns: vec!["old_col".to_string()],
1086            is_unique: false,
1087            is_constraint: false,
1088        };
1089        let new_index = Index {
1090            name: "idx_new".to_string(),
1091            table_name: "users".to_string(),
1092            columns: vec!["new_col".to_string()],
1093            is_unique: true,
1094            is_constraint: false,
1095        };
1096        let old_table = Table {
1097            name: "users".to_string(),
1098            columns: vec![make_column("id", ColumnType::Integer, false, None)],
1099            primary_key_columns: vec![],
1100            indexes: vec![old_index],
1101            foreign_keys: vec![],
1102            comment: None,
1103        };
1104        let new_table = Table {
1105            name: "users".to_string(),
1106            columns: vec![make_column("id", ColumnType::Integer, false, None)],
1107            primary_key_columns: vec![],
1108            indexes: vec![new_index],
1109            foreign_keys: vec![],
1110            comment: None,
1111        };
1112        let mut old_schema = Schema::new(DatabaseType::Postgres);
1113        let mut new_schema = Schema::new(DatabaseType::Postgres);
1114        old_schema.add_table(old_table);
1115        new_schema.add_table(new_table);
1116
1117        let differ = SchemaDiffer::new(old_schema, new_schema);
1118        let migrations = differ.diff();
1119        assert_eq!(migrations.len(), 1);
1120        if let TableChange::AlterTable {
1121            added_indexes,
1122            removed_indexes,
1123            ..
1124        } = &migrations[0].table_changes[0]
1125        {
1126            assert_eq!(added_indexes.len(), 1);
1127            assert_eq!(added_indexes[0].name, "idx_new");
1128            assert_eq!(removed_indexes.len(), 1);
1129            assert_eq!(removed_indexes[0], "idx_old");
1130        } else {
1131            unreachable!("Expected AlterTable");
1132        }
1133    }
1134
1135    #[test]
1136    fn test_diff_added_and_removed_foreign_keys() {
1137        let old_fk = ForeignKey {
1138            name: "fk_old".to_string(),
1139            table_name: "users".to_string(),
1140            column_name: "old_id".to_string(),
1141            referenced_table_name: "old_refs".to_string(),
1142            referenced_column_name: "id".to_string(),
1143            on_delete: None,
1144            on_update: None,
1145        };
1146        let new_fk = ForeignKey {
1147            name: "fk_new".to_string(),
1148            table_name: "users".to_string(),
1149            column_name: "new_id".to_string(),
1150            referenced_table_name: "new_refs".to_string(),
1151            referenced_column_name: "id".to_string(),
1152            on_delete: Some(ForeignKeyAction::Cascade),
1153            on_update: Some(ForeignKeyAction::Restrict),
1154        };
1155        let old_table = Table {
1156            name: "users".to_string(),
1157            columns: vec![make_column("id", ColumnType::Integer, false, None)],
1158            primary_key_columns: vec![],
1159            indexes: vec![],
1160            foreign_keys: vec![old_fk],
1161            comment: None,
1162        };
1163        let new_table = Table {
1164            name: "users".to_string(),
1165            columns: vec![make_column("id", ColumnType::Integer, false, None)],
1166            primary_key_columns: vec![],
1167            indexes: vec![],
1168            foreign_keys: vec![new_fk],
1169            comment: None,
1170        };
1171        let mut old_schema = Schema::new(DatabaseType::Postgres);
1172        let mut new_schema = Schema::new(DatabaseType::Postgres);
1173        old_schema.add_table(old_table);
1174        new_schema.add_table(new_table);
1175
1176        let differ = SchemaDiffer::new(old_schema, new_schema);
1177        let migrations = differ.diff();
1178        assert_eq!(migrations.len(), 1);
1179        if let TableChange::AlterTable {
1180            added_foreign_keys,
1181            removed_foreign_keys,
1182            ..
1183        } = &migrations[0].table_changes[0]
1184        {
1185            assert_eq!(added_foreign_keys.len(), 1);
1186            assert_eq!(added_foreign_keys[0].name, "fk_new");
1187            assert_eq!(removed_foreign_keys.len(), 1);
1188            assert_eq!(removed_foreign_keys[0], "fk_old");
1189        } else {
1190            unreachable!("Expected AlterTable");
1191        }
1192    }
1193
1194    // ===== SqlGenerator 测试 =====
1195
1196    #[test]
1197    fn test_generate_create_table_sql_with_indexes_and_fks() {
1198        let pg = SqlGenerator::new(DatabaseType::Postgres);
1199        let table = Table {
1200            name: "orders".to_string(),
1201            columns: vec![
1202                make_column("id", ColumnType::Integer, false, None),
1203                make_column("user_id", ColumnType::Integer, false, None),
1204            ],
1205            primary_key_columns: vec!["id".to_string()],
1206            indexes: vec![Index {
1207                name: "idx_user_id".to_string(),
1208                table_name: "orders".to_string(),
1209                columns: vec!["user_id".to_string()],
1210                is_unique: false,
1211                is_constraint: false,
1212            }],
1213            foreign_keys: vec![ForeignKey {
1214                name: "fk_user".to_string(),
1215                table_name: "orders".to_string(),
1216                column_name: "user_id".to_string(),
1217                referenced_table_name: "users".to_string(),
1218                referenced_column_name: "id".to_string(),
1219                on_delete: Some(ForeignKeyAction::Cascade),
1220                on_update: None,
1221            }],
1222            comment: None,
1223        };
1224
1225        let sql = pg.generate_create_table_sql(&table).expect("SQL generation failed");
1226        assert!(sql.contains("CREATE TABLE orders"));
1227        assert!(sql.contains("PRIMARY KEY (id)"));
1228        assert!(sql.contains("CREATE INDEX idx_user_id ON orders (user_id)"));
1229        assert!(sql.contains("ALTER TABLE orders ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id)"));
1230        assert!(sql.contains("ON DELETE CASCADE"));
1231    }
1232
1233    #[test]
1234    fn test_generate_create_table_sql_unique_index() {
1235        let pg = SqlGenerator::new(DatabaseType::Postgres);
1236        let table = Table {
1237            name: "users".to_string(),
1238            columns: vec![make_column("email", ColumnType::String(None), false, None)],
1239            primary_key_columns: vec![],
1240            indexes: vec![Index {
1241                name: "idx_email".to_string(),
1242                table_name: "users".to_string(),
1243                columns: vec!["email".to_string()],
1244                is_unique: true,
1245                is_constraint: false,
1246            }],
1247            foreign_keys: vec![],
1248            comment: None,
1249        };
1250
1251        let sql = pg.generate_create_table_sql(&table).expect("SQL generation failed");
1252        assert!(sql.contains("CREATE UNIQUE INDEX idx_email ON users (email)"));
1253    }
1254
1255    #[test]
1256    fn test_generate_create_table_sql_invalid_name() {
1257        let pg = SqlGenerator::new(DatabaseType::Postgres);
1258        let table = Table {
1259            name: "1invalid".to_string(),
1260            columns: vec![],
1261            primary_key_columns: vec![],
1262            indexes: vec![],
1263            foreign_keys: vec![],
1264            comment: None,
1265        };
1266        let result = pg.generate_create_table_sql(&table);
1267        assert!(result.is_err());
1268    }
1269
1270    #[test]
1271    fn test_generate_create_table_sql_mysql_auto_increment() {
1272        let mysql = SqlGenerator::new(DatabaseType::MySql);
1273        let table = Table {
1274            name: "users".to_string(),
1275            columns: vec![Column {
1276                name: "id".to_string(),
1277                column_type: ColumnType::Integer,
1278                is_primary_key: true,
1279                is_nullable: false,
1280                has_default: false,
1281                default_value: None,
1282                is_auto_increment: true,
1283                comment: None,
1284            }],
1285            primary_key_columns: vec!["id".to_string()],
1286            indexes: vec![],
1287            foreign_keys: vec![],
1288            comment: None,
1289        };
1290
1291        let sql = mysql.generate_create_table_sql(&table).expect("SQL generation failed");
1292        assert!(sql.contains("AUTO_INCREMENT"));
1293    }
1294
1295    #[test]
1296    fn test_generate_create_table_sql_sqlite_auto_increment() {
1297        let sqlite = SqlGenerator::new(DatabaseType::Sqlite);
1298        let table = Table {
1299            name: "users".to_string(),
1300            columns: vec![Column {
1301                name: "id".to_string(),
1302                column_type: ColumnType::Integer,
1303                is_primary_key: true,
1304                is_nullable: false,
1305                has_default: false,
1306                default_value: None,
1307                is_auto_increment: true,
1308                comment: None,
1309            }],
1310            primary_key_columns: vec!["id".to_string()],
1311            indexes: vec![],
1312            foreign_keys: vec![],
1313            comment: None,
1314        };
1315
1316        let sql = sqlite.generate_create_table_sql(&table).expect("SQL generation failed");
1317        assert!(sql.contains("PRIMARY KEY AUTOINCREMENT"));
1318    }
1319
1320    #[test]
1321    fn test_generate_create_table_sql_with_default_value() {
1322        let pg = SqlGenerator::new(DatabaseType::Postgres);
1323        let table = Table {
1324            name: "users".to_string(),
1325            columns: vec![Column {
1326                name: "status".to_string(),
1327                column_type: ColumnType::Integer,
1328                is_primary_key: false,
1329                is_nullable: false,
1330                has_default: true,
1331                default_value: Some("0".to_string()),
1332                is_auto_increment: false,
1333                comment: None,
1334            }],
1335            primary_key_columns: vec![],
1336            indexes: vec![],
1337            foreign_keys: vec![],
1338            comment: None,
1339        };
1340
1341        let sql = pg.generate_create_table_sql(&table).expect("SQL generation failed");
1342        assert!(sql.contains("DEFAULT 0"));
1343    }
1344
1345    #[test]
1346    fn test_generate_create_table_sql_nullable_column() {
1347        let pg = SqlGenerator::new(DatabaseType::Postgres);
1348        let table = Table {
1349            name: "users".to_string(),
1350            columns: vec![make_column("bio", ColumnType::Text, true, None)],
1351            primary_key_columns: vec![],
1352            indexes: vec![],
1353            foreign_keys: vec![],
1354            comment: None,
1355        };
1356
1357        let sql = pg.generate_create_table_sql(&table).expect("SQL generation failed");
1358        // nullable 列不应包含 NOT NULL
1359        assert!(!sql.contains("NOT NULL"));
1360    }
1361
1362    #[test]
1363    fn test_generate_create_index_sql_basic() {
1364        let pg = SqlGenerator::new(DatabaseType::Postgres);
1365        let index = Index {
1366            name: "idx_name".to_string(),
1367            table_name: "users".to_string(),
1368            columns: vec!["name".to_string()],
1369            is_unique: false,
1370            is_constraint: false,
1371        };
1372
1373        let sql = pg.generate_create_index_sql(&index).expect("SQL generation failed");
1374        assert_eq!(sql, "CREATE INDEX idx_name ON users (name)");
1375    }
1376
1377    #[test]
1378    fn test_generate_create_index_sql_unique_multi_column() {
1379        let pg = SqlGenerator::new(DatabaseType::Postgres);
1380        let index = Index {
1381            name: "idx_multi".to_string(),
1382            table_name: "users".to_string(),
1383            columns: vec!["first_name".to_string(), "last_name".to_string()],
1384            is_unique: true,
1385            is_constraint: false,
1386        };
1387
1388        let sql = pg.generate_create_index_sql(&index).expect("SQL generation failed");
1389        assert_eq!(sql, "CREATE UNIQUE INDEX idx_multi ON users (first_name, last_name)");
1390    }
1391
1392    #[test]
1393    fn test_generate_create_index_sql_invalid_name() {
1394        let pg = SqlGenerator::new(DatabaseType::Postgres);
1395        let index = Index {
1396            name: "1invalid".to_string(),
1397            table_name: "users".to_string(),
1398            columns: vec!["name".to_string()],
1399            is_unique: false,
1400            is_constraint: false,
1401        };
1402
1403        let result = pg.generate_create_index_sql(&index);
1404        assert!(result.is_err());
1405    }
1406
1407    #[test]
1408    fn test_generate_create_index_sql_invalid_column() {
1409        let pg = SqlGenerator::new(DatabaseType::Postgres);
1410        let index = Index {
1411            name: "idx".to_string(),
1412            table_name: "users".to_string(),
1413            columns: vec!["invalid-col".to_string()],
1414            is_unique: false,
1415            is_constraint: false,
1416        };
1417
1418        let result = pg.generate_create_index_sql(&index);
1419        assert!(result.is_err());
1420    }
1421
1422    #[test]
1423    fn test_generate_drop_table_sql() {
1424        let pg = SqlGenerator::new(DatabaseType::Postgres);
1425        let sql = pg.generate_drop_table_sql("users").expect("SQL generation failed");
1426        assert_eq!(sql, "DROP TABLE users;");
1427    }
1428
1429    #[test]
1430    fn test_generate_drop_table_sql_invalid_name() {
1431        let pg = SqlGenerator::new(DatabaseType::Postgres);
1432        let result = pg.generate_drop_table_sql("1invalid");
1433        assert!(result.is_err());
1434    }
1435
1436    #[test]
1437    fn test_generate_add_column_sql() {
1438        let pg = SqlGenerator::new(DatabaseType::Postgres);
1439        let column = make_column("age", ColumnType::Integer, true, None);
1440        let sql = pg
1441            .generate_add_column_sql("users", &column)
1442            .expect("SQL generation failed");
1443        assert_eq!(sql, "ALTER TABLE users ADD age INTEGER;");
1444    }
1445
1446    #[test]
1447    fn test_generate_add_column_sql_with_default() {
1448        let pg = SqlGenerator::new(DatabaseType::Postgres);
1449        let column = Column {
1450            name: "status".to_string(),
1451            column_type: ColumnType::Integer,
1452            is_primary_key: false,
1453            is_nullable: false,
1454            has_default: true,
1455            default_value: Some("0".to_string()),
1456            is_auto_increment: false,
1457            comment: None,
1458        };
1459        let sql = pg
1460            .generate_add_column_sql("users", &column)
1461            .expect("SQL generation failed");
1462        assert!(sql.contains("DEFAULT 0"));
1463    }
1464
1465    #[test]
1466    fn test_generate_add_column_sql_invalid_table() {
1467        let pg = SqlGenerator::new(DatabaseType::Postgres);
1468        let column = make_column("age", ColumnType::Integer, true, None);
1469        let result = pg.generate_add_column_sql("1invalid", &column);
1470        assert!(result.is_err());
1471    }
1472
1473    #[test]
1474    fn test_generate_drop_column_sql_mysql() {
1475        let mysql = SqlGenerator::new(DatabaseType::MySql);
1476        let sql = mysql
1477            .generate_drop_column_sql("users", "age")
1478            .expect("SQL generation failed");
1479        assert_eq!(sql, "ALTER TABLE users DROP COLUMN age;");
1480    }
1481
1482    #[test]
1483    fn test_generate_drop_column_sql_postgres() {
1484        let pg = SqlGenerator::new(DatabaseType::Postgres);
1485        let sql = pg
1486            .generate_drop_column_sql("users", "age")
1487            .expect("SQL generation failed");
1488        assert_eq!(sql, "ALTER TABLE users DROP COLUMN age;");
1489    }
1490
1491    #[test]
1492    fn test_generate_drop_column_sql_sqlite() {
1493        let sqlite = SqlGenerator::new(DatabaseType::Sqlite);
1494        let sql = sqlite
1495            .generate_drop_column_sql("users", "age")
1496            .expect("SQL generation failed");
1497        assert!(sql.contains("-- SQLite 不支持直接删除列"));
1498        assert!(sql.contains("ALTER TABLE users DROP COLUMN age;"));
1499    }
1500
1501    #[test]
1502    fn test_generate_drop_column_sql_invalid_table() {
1503        let pg = SqlGenerator::new(DatabaseType::Postgres);
1504        let result = pg.generate_drop_column_sql("1invalid", "age");
1505        assert!(result.is_err());
1506    }
1507
1508    #[test]
1509    fn test_generate_migration_sql_create_table() {
1510        let pg = SqlGenerator::new(DatabaseType::Postgres);
1511        let mut migration = Migration::new(1, "create users".to_string());
1512        migration.add_table_change(TableChange::CreateTable(make_table(
1513            "users",
1514            vec![make_column("id", ColumnType::Integer, false, None)],
1515        )));
1516
1517        let sql = pg.generate_migration_sql(&migration).expect("SQL generation failed");
1518        assert!(sql.contains("-- 创建表: users"));
1519        assert!(sql.contains("CREATE TABLE users"));
1520    }
1521
1522    #[test]
1523    fn test_generate_migration_sql_drop_table() {
1524        let pg = SqlGenerator::new(DatabaseType::Postgres);
1525        let mut migration = Migration::new(1, "drop users".to_string());
1526        migration.add_table_change(TableChange::DropTable {
1527            table_name: "users".to_string(),
1528        });
1529
1530        let sql = pg.generate_migration_sql(&migration).expect("SQL generation failed");
1531        assert!(sql.contains("-- 删除表: users"));
1532        assert!(sql.contains("DROP TABLE users;"));
1533    }
1534
1535    #[test]
1536    fn test_generate_migration_sql_alter_table_full() {
1537        let pg = SqlGenerator::new(DatabaseType::Postgres);
1538        let mut migration = Migration::new(1, "alter users".to_string());
1539        migration.add_table_change(TableChange::AlterTable {
1540            table_name: "users".to_string(),
1541            column_changes: vec![],
1542            added_columns: vec![make_column("age", ColumnType::Integer, true, None)],
1543            removed_columns: vec!["old_col".to_string()],
1544            added_indexes: vec![Index {
1545                name: "idx_age".to_string(),
1546                table_name: "users".to_string(),
1547                columns: vec!["age".to_string()],
1548                is_unique: false,
1549                is_constraint: false,
1550            }],
1551            removed_indexes: vec!["idx_old".to_string()],
1552            added_foreign_keys: vec![ForeignKey {
1553                name: "fk_role".to_string(),
1554                table_name: "users".to_string(),
1555                column_name: "role_id".to_string(),
1556                referenced_table_name: "roles".to_string(),
1557                referenced_column_name: "id".to_string(),
1558                on_delete: Some(ForeignKeyAction::SetNull),
1559                on_update: None,
1560            }],
1561            removed_foreign_keys: vec!["fk_old".to_string()],
1562        });
1563
1564        let sql = pg.generate_migration_sql(&migration).expect("SQL generation failed");
1565        assert!(sql.contains("-- 修改表: users"));
1566        assert!(sql.contains("-- 添加列: age"));
1567        assert!(sql.contains("ALTER TABLE users ADD age INTEGER;"));
1568        assert!(sql.contains("-- 删除列: old_col"));
1569        assert!(sql.contains("ALTER TABLE users DROP COLUMN old_col;"));
1570        assert!(sql.contains("-- 添加索引: idx_age"));
1571        assert!(sql.contains("CREATE INDEX idx_age ON users (age)"));
1572        assert!(sql.contains("-- 删除索引: idx_old"));
1573        assert!(sql.contains("DROP INDEX idx_old;"));
1574        assert!(sql.contains("-- 添加外键: fk_role"));
1575        assert!(sql.contains("ALTER TABLE users ADD CONSTRAINT fk_role FOREIGN KEY (role_id) REFERENCES roles(id)"));
1576        assert!(sql.contains("ON DELETE SET NULL"));
1577        assert!(sql.contains("-- 删除外键: fk_old"));
1578        assert!(sql.contains("ALTER TABLE users DROP CONSTRAINT fk_old;"));
1579    }
1580
1581    #[test]
1582    fn test_generate_migration_sql_empty() {
1583        let pg = SqlGenerator::new(DatabaseType::Postgres);
1584        let migration = Migration::new(1, "empty".to_string());
1585
1586        let sql = pg.generate_migration_sql(&migration).expect("SQL generation failed");
1587        assert!(sql.is_empty());
1588    }
1589
1590    #[test]
1591    fn test_generate_migration_sql_invalid_table_in_create() {
1592        let pg = SqlGenerator::new(DatabaseType::Postgres);
1593        let mut migration = Migration::new(1, "bad".to_string());
1594        migration.add_table_change(TableChange::CreateTable(make_table(
1595            "1invalid",
1596            vec![make_column("id", ColumnType::Integer, false, None)],
1597        )));
1598
1599        let result = pg.generate_migration_sql(&migration);
1600        assert!(result.is_err());
1601    }
1602
1603    // ===== MigrationCommand / MigrationPlan 类型测试 =====
1604
1605    #[test]
1606    fn test_migration_direction_variants() {
1607        let up = MigrationDirection::Up;
1608        let down = MigrationDirection::Down;
1609        assert!(matches!(up, MigrationDirection::Up));
1610        assert!(matches!(down, MigrationDirection::Down));
1611    }
1612
1613    #[test]
1614    fn test_migration_plan_construction() {
1615        let plan = MigrationPlan {
1616            migrations: vec![Migration::new(1, "v1".to_string())],
1617            direction: MigrationDirection::Up,
1618        };
1619        assert_eq!(plan.migrations.len(), 1);
1620        assert!(matches!(plan.direction, MigrationDirection::Up));
1621    }
1622
1623    #[test]
1624    fn test_migration_command_create() {
1625        let cmd = MigrationCommand::Create {
1626            description: "init".to_string(),
1627            directory: "migrations".to_string(),
1628        };
1629        match cmd {
1630            MigrationCommand::Create { description, directory } => {
1631                assert_eq!(description, "init");
1632                assert_eq!(directory, "migrations");
1633            }
1634            _ => panic!("expected Create"),
1635        }
1636    }
1637
1638    #[test]
1639    fn test_migration_command_up_with_target() {
1640        let cmd = MigrationCommand::Up {
1641            target_version: Some(5),
1642        };
1643        match cmd {
1644            MigrationCommand::Up {
1645                target_version: Some(5),
1646            } => {}
1647            _ => panic!("expected Up with target 5"),
1648        }
1649    }
1650
1651    #[test]
1652    fn test_migration_command_up_no_target() {
1653        let cmd = MigrationCommand::Up { target_version: None };
1654        match cmd {
1655            MigrationCommand::Up { target_version: None } => {}
1656            _ => panic!("expected Up with no target"),
1657        }
1658    }
1659
1660    #[test]
1661    fn test_migration_command_down() {
1662        let cmd = MigrationCommand::Down {
1663            target_version: Some(2),
1664        };
1665        match cmd {
1666            MigrationCommand::Down {
1667                target_version: Some(2),
1668            } => {}
1669            _ => panic!("expected Down with target 2"),
1670        }
1671    }
1672
1673    #[test]
1674    fn test_migration_command_status() {
1675        let cmd = MigrationCommand::Status;
1676        assert!(matches!(cmd, MigrationCommand::Status));
1677    }
1678
1679    #[test]
1680    fn test_migration_command_generate() {
1681        let cmd = MigrationCommand::Generate {
1682            from_schema: "old".to_string(),
1683            to_schema: "new".to_string(),
1684            output_file: "out.sql".to_string(),
1685        };
1686        match cmd {
1687            MigrationCommand::Generate {
1688                from_schema,
1689                to_schema,
1690                output_file,
1691            } => {
1692                assert_eq!(from_schema, "old");
1693                assert_eq!(to_schema, "new");
1694                assert_eq!(output_file, "out.sql");
1695            }
1696            _ => panic!("expected Generate"),
1697        }
1698    }
1699}