1use super::schema::*;
11use super::types::*;
12use crate::foundation::config::DatabaseType;
13use regex::Regex;
14use std::sync::LazyLock;
15
16fn 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 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 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
90fn sanitize_default_value(default: &str) -> String {
92 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 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 trimmed.to_string()
137 } else {
138 format!("'{}'", trimmed.replace('\'', "''"))
140 }
141}
142
143pub struct MigrationPlan {
145 pub migrations: Vec<Migration>,
147 pub direction: MigrationDirection,
149}
150
151#[derive(Debug, Clone)]
153pub enum MigrationDirection {
154 Up,
156 Down,
158}
159
160#[derive(Debug, Clone)]
162pub enum MigrationCommand {
163 Create {
165 description: String,
167 directory: String,
169 },
170 Up {
172 target_version: Option<u32>,
174 },
175 Down {
177 target_version: Option<u32>,
179 },
180 Status,
182 Generate {
184 from_schema: String,
186 to_schema: String,
188 output_file: String,
190 },
191}
192
193pub struct SchemaDiffer {
195 old_schema: Schema,
197 new_schema: Schema,
199}
200
201impl SchemaDiffer {
202 pub fn new(old_schema: Schema, new_schema: Schema) -> Self {
204 Self { old_schema, new_schema }
205 }
206
207 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 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 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 for new_table in &self.new_schema.tables {
230 if let Some(old_table) = self.old_schema.get_table(&new_table.name) {
231 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 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 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 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 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 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 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 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 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 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 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#[derive(Debug, Clone)]
370pub struct SqlGenerator {
371 pub db_type: DatabaseType,
373}
374
375impl SqlGenerator {
376 pub fn new(db_type: DatabaseType) -> Self {
378 Self { db_type }
379 }
380
381 pub fn generate_column_def(&self, column_type: &ColumnType) -> String {
383 column_type.to_sql(self.db_type)
384 }
385
386 pub fn generate_create_table_sql(&self, table: &Table) -> Result<String, String> {
388 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 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 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 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 fn generate_column_definition(&self, column: &Column, _pk_columns: &[String]) -> Result<String, String> {
449 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 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 if column.is_primary_key && !column.is_auto_increment {
475 }
477
478 Ok(def)
479 }
480
481 pub fn generate_create_index_sql(&self, index: &Index) -> Result<String, String> {
483 let index_name = validate_sql_identifier(&index.name, "索引名")?;
485
486 let table_name = validate_sql_identifier(&index.table_name, "表名")?;
488
489 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 fn generate_add_foreign_key_sql(&self, fk: &ForeignKey) -> Result<String, String> {
508 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 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 pub fn generate_add_column_sql(&self, table_name: &str, column: &Column) -> Result<String, String> {
540 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 pub fn generate_drop_column_sql(&self, table_name: &str, column_name: &str) -> Result<String, String> {
553 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 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 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]
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 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 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]
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 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]
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 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]
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 #[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 #[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 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 #[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 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 #[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}