1pub mod add_column;
2pub mod add_constraint;
3pub mod create_table;
4pub mod delete_column;
5pub mod delete_table;
6pub mod helpers;
7pub mod modify_column_comment;
8pub mod modify_column_default;
9pub mod modify_column_nullable;
10pub mod modify_column_type;
11pub mod raw_sql;
12pub mod remove_constraint;
13pub mod rename_column;
14pub mod rename_table;
15pub mod replace_constraint;
16pub mod types;
17
18pub use helpers::*;
19pub use types::{BuiltQuery, DatabaseBackend, RawSql};
20
21use crate::error::QueryError;
22use vespertide_core::{MigrationAction, TableConstraint, TableDef};
23
24use self::{
25 add_column::build_add_column, add_constraint::build_add_constraint,
26 create_table::build_create_table, delete_column::build_delete_column,
27 delete_table::build_delete_table, modify_column_comment::build_modify_column_comment,
28 modify_column_default::build_modify_column_default,
29 modify_column_nullable::build_modify_column_nullable,
30 modify_column_type::build_modify_column_type, raw_sql::build_raw_sql,
31 remove_constraint::build_remove_constraint, rename_column::build_rename_column,
32 rename_table::build_rename_table, replace_constraint::build_replace_constraint,
33};
34
35pub fn build_action_queries(
36 backend: &DatabaseBackend,
37 action: &MigrationAction,
38 current_schema: &[TableDef],
39) -> Result<Vec<BuiltQuery>, QueryError> {
40 build_action_queries_with_pending(backend, action, current_schema, &[])
41}
42
43pub fn build_action_queries_with_pending(
49 backend: &DatabaseBackend,
50 action: &MigrationAction,
51 current_schema: &[TableDef],
52 pending_constraints: &[TableConstraint],
53) -> Result<Vec<BuiltQuery>, QueryError> {
54 match action {
55 MigrationAction::CreateTable {
56 table,
57 columns,
58 constraints,
59 } => build_create_table(backend, table, columns, constraints),
60
61 MigrationAction::DeleteTable { table } => Ok(vec![build_delete_table(table)]),
62
63 MigrationAction::AddColumn {
64 table,
65 column,
66 fill_with,
67 } => build_add_column(
68 backend,
69 table,
70 column,
71 fill_with.as_deref(),
72 current_schema,
73 pending_constraints,
74 ),
75
76 MigrationAction::RenameColumn { table, from, to } => {
77 Ok(vec![build_rename_column(table, from, to)])
78 }
79
80 MigrationAction::DeleteColumn { table, column } => {
81 let column_type = current_schema
83 .iter()
84 .find(|t| t.name == *table)
85 .and_then(|t| t.columns.iter().find(|c| c.name == *column))
86 .map(|c| &c.r#type);
87 Ok(build_delete_column(
88 backend,
89 table,
90 column,
91 column_type,
92 current_schema,
93 pending_constraints,
94 ))
95 }
96
97 MigrationAction::ModifyColumnType {
98 table,
99 column,
100 new_type,
101 fill_with,
102 } => build_modify_column_type(
103 backend,
104 table,
105 column,
106 new_type,
107 fill_with.as_ref(),
108 current_schema,
109 pending_constraints,
110 ),
111
112 MigrationAction::ModifyColumnNullable {
113 table,
114 column,
115 nullable,
116 fill_with,
117 delete_null_rows,
118 } => build_modify_column_nullable(
119 backend,
120 table,
121 column,
122 *nullable,
123 fill_with.as_deref(),
124 delete_null_rows.unwrap_or(false),
125 current_schema,
126 pending_constraints,
127 ),
128
129 MigrationAction::ModifyColumnDefault {
130 table,
131 column,
132 new_default,
133 } => build_modify_column_default(
134 backend,
135 table,
136 column,
137 new_default.as_deref(),
138 current_schema,
139 pending_constraints,
140 ),
141
142 MigrationAction::ModifyColumnComment {
143 table,
144 column,
145 new_comment,
146 } => build_modify_column_comment(
147 backend,
148 table,
149 column,
150 new_comment.as_deref(),
151 current_schema,
152 ),
153
154 MigrationAction::RenameTable { from, to } => Ok(vec![build_rename_table(from, to)]),
155
156 MigrationAction::RawSql { sql } => Ok(vec![build_raw_sql(sql.clone())]),
157
158 MigrationAction::AddConstraint { table, constraint } => build_add_constraint(
159 backend,
160 table,
161 constraint,
162 current_schema,
163 pending_constraints,
164 ),
165
166 MigrationAction::RemoveConstraint { table, constraint } => build_remove_constraint(
167 backend,
168 table,
169 constraint,
170 current_schema,
171 pending_constraints,
172 ),
173
174 MigrationAction::ReplaceConstraint { table, from, to } => build_replace_constraint(
175 backend,
176 table,
177 from,
178 to,
179 current_schema,
180 pending_constraints,
181 ),
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use insta::{assert_snapshot, with_settings};
189 use rstest::rstest;
190 use vespertide_core::schema::primary_key::PrimaryKeySyntax;
191 use vespertide_core::{
192 ColumnDef, ColumnType, MigrationAction, ReferenceAction, SimpleColumnType, TableConstraint,
193 };
194
195 fn col(name: &str, ty: ColumnType) -> ColumnDef {
196 ColumnDef {
197 name: name.to_string(),
198 r#type: ty,
199 nullable: true,
200 default: None,
201 comment: None,
202 primary_key: None,
203 unique: None,
204 index: None,
205 foreign_key: None,
206 }
207 }
208
209 #[test]
210 fn test_backend_specific_quoting() {
211 let action = MigrationAction::CreateTable {
212 table: "users".into(),
213 columns: vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))],
214 constraints: vec![],
215 };
216 let result = build_action_queries(&DatabaseBackend::Postgres, &action, &[]).unwrap();
217
218 let pg_sql = result[0].build(DatabaseBackend::Postgres);
220 assert!(pg_sql.contains("\"users\""));
221
222 let mysql_sql = result[0].build(DatabaseBackend::MySql);
224 assert!(mysql_sql.contains("`users`"));
225
226 let sqlite_sql = result[0].build(DatabaseBackend::Sqlite);
228 assert!(sqlite_sql.contains("\"users\""));
229 }
230
231 #[rstest]
232 #[case::create_table_with_default_postgres(
233 "create_table_with_default_postgres",
234 MigrationAction::CreateTable {
235 table: "users".into(),
236 columns: vec![ColumnDef {
237 name: "status".into(),
238 r#type: ColumnType::Simple(SimpleColumnType::Text),
239 nullable: true,
240 default: Some("'active'".into()),
241 comment: None,
242 primary_key: None,
243 unique: None,
244 index: None,
245 foreign_key: None,
246 }],
247 constraints: vec![],
248 },
249 DatabaseBackend::Postgres,
250 &["DEFAULT", "'active'"]
251 )]
252 #[case::create_table_with_default_mysql(
253 "create_table_with_default_mysql",
254 MigrationAction::CreateTable {
255 table: "users".into(),
256 columns: vec![ColumnDef {
257 name: "status".into(),
258 r#type: ColumnType::Simple(SimpleColumnType::Text),
259 nullable: true,
260 default: Some("'active'".into()),
261 comment: None,
262 primary_key: None,
263 unique: None,
264 index: None,
265 foreign_key: None,
266 }],
267 constraints: vec![],
268 },
269 DatabaseBackend::Postgres,
270 &["DEFAULT", "'active'"]
271 )]
272 #[case::create_table_with_default_sqlite(
273 "create_table_with_default_sqlite",
274 MigrationAction::CreateTable {
275 table: "users".into(),
276 columns: vec![ColumnDef {
277 name: "status".into(),
278 r#type: ColumnType::Simple(SimpleColumnType::Text),
279 nullable: true,
280 default: Some("'active'".into()),
281 comment: None,
282 primary_key: None,
283 unique: None,
284 index: None,
285 foreign_key: None,
286 }],
287 constraints: vec![],
288 },
289 DatabaseBackend::Postgres,
290 &["DEFAULT", "'active'"]
291 )]
292 #[case::create_table_with_inline_primary_key_postgres(
293 "create_table_with_inline_primary_key_postgres",
294 MigrationAction::CreateTable {
295 table: "users".into(),
296 columns: vec![ColumnDef {
297 name: "id".into(),
298 r#type: ColumnType::Simple(SimpleColumnType::Integer),
299 nullable: false,
300 default: None,
301 comment: None,
302 primary_key: Some(PrimaryKeySyntax::Bool(true)),
303 unique: None,
304 index: None,
305 foreign_key: None,
306 }],
307 constraints: vec![],
308 },
309 DatabaseBackend::Postgres,
310 &["PRIMARY KEY"]
311 )]
312 #[case::create_table_with_inline_primary_key_mysql(
313 "create_table_with_inline_primary_key_mysql",
314 MigrationAction::CreateTable {
315 table: "users".into(),
316 columns: vec![ColumnDef {
317 name: "id".into(),
318 r#type: ColumnType::Simple(SimpleColumnType::Integer),
319 nullable: false,
320 default: None,
321 comment: None,
322 primary_key: Some(PrimaryKeySyntax::Bool(true)),
323 unique: None,
324 index: None,
325 foreign_key: None,
326 }],
327 constraints: vec![],
328 },
329 DatabaseBackend::Postgres,
330 &["PRIMARY KEY"]
331 )]
332 #[case::create_table_with_inline_primary_key_sqlite(
333 "create_table_with_inline_primary_key_sqlite",
334 MigrationAction::CreateTable {
335 table: "users".into(),
336 columns: vec![ColumnDef {
337 name: "id".into(),
338 r#type: ColumnType::Simple(SimpleColumnType::Integer),
339 nullable: false,
340 default: None,
341 comment: None,
342 primary_key: Some(PrimaryKeySyntax::Bool(true)),
343 unique: None,
344 index: None,
345 foreign_key: None,
346 }],
347 constraints: vec![],
348 },
349 DatabaseBackend::Postgres,
350 &["PRIMARY KEY"]
351 )]
352 #[case::create_table_with_fk_postgres(
353 "create_table_with_fk_postgres",
354 MigrationAction::CreateTable {
355 table: "posts".into(),
356 columns: vec![
357 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
358 col("user_id", ColumnType::Simple(SimpleColumnType::Integer)),
359 ],
360 constraints: vec![TableConstraint::ForeignKey {
361 name: Some("fk_user".into()),
362 columns: vec!["user_id".into()],
363 ref_table: "users".into(),
364 ref_columns: vec!["id".into()],
365 on_delete: Some(ReferenceAction::Cascade),
366 on_update: Some(ReferenceAction::Restrict),
367 }],
368 },
369 DatabaseBackend::Postgres,
370 &["REFERENCES \"users\" (\"id\")", "ON DELETE CASCADE", "ON UPDATE RESTRICT"]
371 )]
372 #[case::create_table_with_fk_mysql(
373 "create_table_with_fk_mysql",
374 MigrationAction::CreateTable {
375 table: "posts".into(),
376 columns: vec![
377 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
378 col("user_id", ColumnType::Simple(SimpleColumnType::Integer)),
379 ],
380 constraints: vec![TableConstraint::ForeignKey {
381 name: Some("fk_user".into()),
382 columns: vec!["user_id".into()],
383 ref_table: "users".into(),
384 ref_columns: vec!["id".into()],
385 on_delete: Some(ReferenceAction::Cascade),
386 on_update: Some(ReferenceAction::Restrict),
387 }],
388 },
389 DatabaseBackend::Postgres,
390 &["REFERENCES \"users\" (\"id\")", "ON DELETE CASCADE", "ON UPDATE RESTRICT"]
391 )]
392 #[case::create_table_with_fk_sqlite(
393 "create_table_with_fk_sqlite",
394 MigrationAction::CreateTable {
395 table: "posts".into(),
396 columns: vec![
397 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
398 col("user_id", ColumnType::Simple(SimpleColumnType::Integer)),
399 ],
400 constraints: vec![TableConstraint::ForeignKey {
401 name: Some("fk_user".into()),
402 columns: vec!["user_id".into()],
403 ref_table: "users".into(),
404 ref_columns: vec!["id".into()],
405 on_delete: Some(ReferenceAction::Cascade),
406 on_update: Some(ReferenceAction::Restrict),
407 }],
408 },
409 DatabaseBackend::Postgres,
410 &["REFERENCES \"users\" (\"id\")", "ON DELETE CASCADE", "ON UPDATE RESTRICT"]
411 )]
412 fn test_build_migration_action(
413 #[case] title: &str,
414 #[case] action: MigrationAction,
415 #[case] backend: DatabaseBackend,
416 #[case] expected: &[&str],
417 ) {
418 let result = build_action_queries(&backend, &action, &[]).unwrap();
419 let sql = result[0].build(backend);
420 for exp in expected {
421 assert!(
422 sql.contains(exp),
423 "Expected SQL to contain '{}', got: {}",
424 exp,
425 sql
426 );
427 }
428
429 with_settings!({ snapshot_suffix => format!("build_migration_action_{}", title) }, {
430 assert_snapshot!(result.iter().map(|q| q.build(backend)).collect::<Vec<String>>().join("\n"));
431 });
432 }
433
434 #[rstest]
435 #[case::rename_column_postgres(DatabaseBackend::Postgres)]
436 #[case::rename_column_mysql(DatabaseBackend::MySql)]
437 #[case::rename_column_sqlite(DatabaseBackend::Sqlite)]
438 fn test_build_action_queries_rename_column(#[case] backend: DatabaseBackend) {
439 let action = MigrationAction::RenameColumn {
441 table: "users".into(),
442 from: "old_name".into(),
443 to: "new_name".into(),
444 };
445 let result = build_action_queries(&backend, &action, &[]).unwrap();
446 assert_eq!(result.len(), 1);
447 let sql = result[0].build(backend);
448 assert!(sql.contains("RENAME"));
449 assert!(sql.contains("old_name"));
450 assert!(sql.contains("new_name"));
451
452 with_settings!({ snapshot_suffix => format!("rename_column_{:?}", backend) }, {
453 assert_snapshot!(sql);
454 });
455 }
456
457 #[rstest]
458 #[case::delete_column_postgres(DatabaseBackend::Postgres)]
459 #[case::delete_column_mysql(DatabaseBackend::MySql)]
460 #[case::delete_column_sqlite(DatabaseBackend::Sqlite)]
461 fn test_build_action_queries_delete_column(#[case] backend: DatabaseBackend) {
462 let action = MigrationAction::DeleteColumn {
464 table: "users".into(),
465 column: "email".into(),
466 };
467 let result = build_action_queries(&backend, &action, &[]).unwrap();
468 assert_eq!(result.len(), 1);
469 let sql = result[0].build(backend);
470 assert!(sql.contains("DROP COLUMN"));
471 assert!(sql.contains("email"));
472
473 with_settings!({ snapshot_suffix => format!("delete_column_{:?}", backend) }, {
474 assert_snapshot!(sql);
475 });
476 }
477
478 #[rstest]
479 #[case::modify_column_type_postgres(DatabaseBackend::Postgres)]
480 #[case::modify_column_type_mysql(DatabaseBackend::MySql)]
481 #[case::modify_column_type_sqlite(DatabaseBackend::Sqlite)]
482 fn test_build_action_queries_modify_column_type(#[case] backend: DatabaseBackend) {
483 let action = MigrationAction::ModifyColumnType {
485 table: "users".into(),
486 column: "age".into(),
487 new_type: ColumnType::Simple(SimpleColumnType::BigInt),
488 fill_with: None,
489 };
490 let current_schema = vec![TableDef {
491 name: "users".into(),
492 description: None,
493 columns: vec![ColumnDef {
494 name: "age".into(),
495 r#type: ColumnType::Simple(SimpleColumnType::Integer),
496 nullable: true,
497 default: None,
498 comment: None,
499 primary_key: None,
500 unique: None,
501 index: None,
502 foreign_key: None,
503 }],
504 constraints: vec![],
505 }];
506 let result = build_action_queries(&backend, &action, ¤t_schema).unwrap();
507 assert!(!result.is_empty());
508 let sql = result
509 .iter()
510 .map(|q| q.build(backend))
511 .collect::<Vec<String>>()
512 .join("\n");
513 assert!(sql.contains("ALTER TABLE"));
514
515 with_settings!({ snapshot_suffix => format!("modify_column_type_{:?}", backend) }, {
516 assert_snapshot!(sql);
517 });
518 }
519
520 #[rstest]
521 #[case::remove_index_constraint_postgres(DatabaseBackend::Postgres)]
522 #[case::remove_index_constraint_mysql(DatabaseBackend::MySql)]
523 #[case::remove_index_constraint_sqlite(DatabaseBackend::Sqlite)]
524 fn test_build_action_queries_remove_index_constraint(#[case] backend: DatabaseBackend) {
525 let action = MigrationAction::RemoveConstraint {
527 table: "users".into(),
528 constraint: TableConstraint::Index {
529 name: Some("idx_email".into()),
530 columns: vec!["email".into()],
531 },
532 };
533 let result = build_action_queries(&backend, &action, &[]).unwrap();
534 assert_eq!(result.len(), 1);
535 let sql = result[0].build(backend);
536 assert!(sql.contains("DROP INDEX"));
537 assert!(sql.contains("idx_email"));
538
539 with_settings!({ snapshot_suffix => format!("remove_index_constraint_{:?}", backend) }, {
540 assert_snapshot!(sql);
541 });
542 }
543
544 #[rstest]
545 #[case::rename_table_postgres(DatabaseBackend::Postgres)]
546 #[case::rename_table_mysql(DatabaseBackend::MySql)]
547 #[case::rename_table_sqlite(DatabaseBackend::Sqlite)]
548 fn test_build_action_queries_rename_table(#[case] backend: DatabaseBackend) {
549 let action = MigrationAction::RenameTable {
551 from: "old_table".into(),
552 to: "new_table".into(),
553 };
554 let result = build_action_queries(&backend, &action, &[]).unwrap();
555 assert_eq!(result.len(), 1);
556 let sql = result[0].build(backend);
557 assert!(sql.contains("RENAME"));
558 assert!(sql.contains("old_table"));
559 assert!(sql.contains("new_table"));
560
561 with_settings!({ snapshot_suffix => format!("rename_table_{:?}", backend) }, {
562 assert_snapshot!(sql);
563 });
564 }
565
566 #[rstest]
567 #[case::add_constraint_postgres(DatabaseBackend::Postgres)]
568 #[case::add_constraint_mysql(DatabaseBackend::MySql)]
569 #[case::add_constraint_sqlite(DatabaseBackend::Sqlite)]
570 fn test_build_action_queries_add_constraint(#[case] backend: DatabaseBackend) {
571 let action = MigrationAction::AddConstraint {
573 table: "users".into(),
574 constraint: TableConstraint::Unique {
575 name: Some("uq_email".into()),
576 columns: vec!["email".into()],
577 },
578 };
579 let current_schema = vec![TableDef {
580 name: "users".into(),
581 description: None,
582 columns: vec![
583 ColumnDef {
584 name: "id".into(),
585 r#type: ColumnType::Simple(SimpleColumnType::Integer),
586 nullable: false,
587 default: None,
588 comment: None,
589 primary_key: None,
590 unique: None,
591 index: None,
592 foreign_key: None,
593 },
594 ColumnDef {
595 name: "email".into(),
596 r#type: ColumnType::Simple(SimpleColumnType::Text),
597 nullable: true,
598 default: None,
599 comment: None,
600 primary_key: None,
601 unique: None,
602 index: None,
603 foreign_key: None,
604 },
605 ],
606 constraints: vec![],
607 }];
608 let result = build_action_queries(&backend, &action, ¤t_schema).unwrap();
609 assert!(!result.is_empty());
610 let sql = result
611 .iter()
612 .map(|q| q.build(backend))
613 .collect::<Vec<String>>()
614 .join("\n");
615 assert!(sql.contains("UNIQUE") || sql.contains("uq_email"));
616
617 with_settings!({ snapshot_suffix => format!("add_constraint_{:?}", backend) }, {
618 assert_snapshot!(sql);
619 });
620 }
621
622 #[rstest]
623 #[case::remove_constraint_postgres(DatabaseBackend::Postgres)]
624 #[case::remove_constraint_mysql(DatabaseBackend::MySql)]
625 #[case::remove_constraint_sqlite(DatabaseBackend::Sqlite)]
626 fn test_build_action_queries_remove_constraint(#[case] backend: DatabaseBackend) {
627 let action = MigrationAction::RemoveConstraint {
629 table: "users".into(),
630 constraint: TableConstraint::Unique {
631 name: Some("uq_email".into()),
632 columns: vec!["email".into()],
633 },
634 };
635 let current_schema = vec![TableDef {
636 name: "users".into(),
637 description: None,
638 columns: vec![
639 ColumnDef {
640 name: "id".into(),
641 r#type: ColumnType::Simple(SimpleColumnType::Integer),
642 nullable: false,
643 default: None,
644 comment: None,
645 primary_key: None,
646 unique: None,
647 index: None,
648 foreign_key: None,
649 },
650 ColumnDef {
651 name: "email".into(),
652 r#type: ColumnType::Simple(SimpleColumnType::Text),
653 nullable: true,
654 default: None,
655 comment: None,
656 primary_key: None,
657 unique: None,
658 index: None,
659 foreign_key: None,
660 },
661 ],
662 constraints: vec![TableConstraint::Unique {
663 name: Some("uq_email".into()),
664 columns: vec!["email".into()],
665 }],
666 }];
667 let result = build_action_queries(&backend, &action, ¤t_schema).unwrap();
668 assert!(!result.is_empty());
669 let sql = result
670 .iter()
671 .map(|q| q.build(backend))
672 .collect::<Vec<String>>()
673 .join("\n");
674 assert!(sql.contains("DROP") || sql.contains("CONSTRAINT"));
675
676 with_settings!({ snapshot_suffix => format!("remove_constraint_{:?}", backend) }, {
677 assert_snapshot!(sql);
678 });
679 }
680
681 #[rstest]
682 #[case::add_column_postgres(DatabaseBackend::Postgres)]
683 #[case::add_column_mysql(DatabaseBackend::MySql)]
684 #[case::add_column_sqlite(DatabaseBackend::Sqlite)]
685 fn test_build_action_queries_add_column(#[case] backend: DatabaseBackend) {
686 let action = MigrationAction::AddColumn {
688 table: "users".into(),
689 column: Box::new(ColumnDef {
690 name: "email".into(),
691 r#type: ColumnType::Simple(SimpleColumnType::Text),
692 nullable: true,
693 default: None,
694 comment: None,
695 primary_key: None,
696 unique: None,
697 index: None,
698 foreign_key: None,
699 }),
700 fill_with: None,
701 };
702 let current_schema = vec![TableDef {
703 name: "users".into(),
704 description: None,
705 columns: vec![ColumnDef {
706 name: "id".into(),
707 r#type: ColumnType::Simple(SimpleColumnType::Integer),
708 nullable: false,
709 default: None,
710 comment: None,
711 primary_key: None,
712 unique: None,
713 index: None,
714 foreign_key: None,
715 }],
716 constraints: vec![],
717 }];
718 let result = build_action_queries(&backend, &action, ¤t_schema).unwrap();
719 assert!(!result.is_empty());
720 let sql = result
721 .iter()
722 .map(|q| q.build(backend))
723 .collect::<Vec<String>>()
724 .join("\n");
725 assert!(sql.contains("ALTER TABLE"));
726 assert!(sql.contains("ADD COLUMN") || sql.contains("ADD"));
727
728 with_settings!({ snapshot_suffix => format!("add_column_{:?}", backend) }, {
729 assert_snapshot!(sql);
730 });
731 }
732
733 #[rstest]
734 #[case::add_index_constraint_postgres(DatabaseBackend::Postgres)]
735 #[case::add_index_constraint_mysql(DatabaseBackend::MySql)]
736 #[case::add_index_constraint_sqlite(DatabaseBackend::Sqlite)]
737 fn test_build_action_queries_add_index_constraint(#[case] backend: DatabaseBackend) {
738 let action = MigrationAction::AddConstraint {
740 table: "users".into(),
741 constraint: TableConstraint::Index {
742 name: Some("idx_email".into()),
743 columns: vec!["email".into()],
744 },
745 };
746 let result = build_action_queries(&backend, &action, &[]).unwrap();
747 assert_eq!(result.len(), 1);
748 let sql = result[0].build(backend);
749 assert!(sql.contains("CREATE INDEX"));
750 assert!(sql.contains("idx_email"));
751
752 with_settings!({ snapshot_suffix => format!("add_index_constraint_{:?}", backend) }, {
753 assert_snapshot!(sql);
754 });
755 }
756
757 #[rstest]
758 #[case::raw_sql_postgres(DatabaseBackend::Postgres)]
759 #[case::raw_sql_mysql(DatabaseBackend::MySql)]
760 #[case::raw_sql_sqlite(DatabaseBackend::Sqlite)]
761 fn test_build_action_queries_raw_sql(#[case] backend: DatabaseBackend) {
762 let action = MigrationAction::RawSql {
764 sql: "SELECT 1;".into(),
765 };
766 let result = build_action_queries(&backend, &action, &[]).unwrap();
767 assert_eq!(result.len(), 1);
768 let sql = result[0].build(backend);
769 assert_eq!(sql, "SELECT 1;");
770
771 with_settings!({ snapshot_suffix => format!("raw_sql_{:?}", backend) }, {
772 assert_snapshot!(sql);
773 });
774 }
775
776 #[rstest]
778 #[case::add_index_with_custom_name_postgres(
779 DatabaseBackend::Postgres,
780 "hello",
781 vec!["email", "password"]
782 )]
783 #[case::add_index_with_custom_name_mysql(
784 DatabaseBackend::MySql,
785 "hello",
786 vec!["email", "password"]
787 )]
788 #[case::add_index_with_custom_name_sqlite(
789 DatabaseBackend::Sqlite,
790 "hello",
791 vec!["email", "password"]
792 )]
793 #[case::add_index_single_column_postgres(
794 DatabaseBackend::Postgres,
795 "email_idx",
796 vec!["email"]
797 )]
798 #[case::add_index_single_column_mysql(
799 DatabaseBackend::MySql,
800 "email_idx",
801 vec!["email"]
802 )]
803 #[case::add_index_single_column_sqlite(
804 DatabaseBackend::Sqlite,
805 "email_idx",
806 vec!["email"]
807 )]
808 fn test_add_index_with_custom_name(
809 #[case] backend: DatabaseBackend,
810 #[case] index_name: &str,
811 #[case] columns: Vec<&str>,
812 ) {
813 let action = MigrationAction::AddConstraint {
815 table: "user".into(),
816 constraint: TableConstraint::Index {
817 name: Some(index_name.into()),
818 columns: columns.iter().map(|s| s.to_string()).collect(),
819 },
820 };
821 let result = build_action_queries(&backend, &action, &[]).unwrap();
822 let sql = result[0].build(backend);
823
824 let expected_name = format!("ix_user__{}", index_name);
826 assert!(
827 sql.contains(&expected_name),
828 "Expected index name '{}' in SQL: {}",
829 expected_name,
830 sql
831 );
832
833 with_settings!({ snapshot_suffix => format!("add_index_custom_{}_{:?}", index_name, backend) }, {
834 assert_snapshot!(sql);
835 });
836 }
837
838 #[rstest]
839 #[case::add_unnamed_index_single_column_postgres(
840 DatabaseBackend::Postgres,
841 vec!["email"]
842 )]
843 #[case::add_unnamed_index_single_column_mysql(
844 DatabaseBackend::MySql,
845 vec!["email"]
846 )]
847 #[case::add_unnamed_index_single_column_sqlite(
848 DatabaseBackend::Sqlite,
849 vec!["email"]
850 )]
851 #[case::add_unnamed_index_multiple_columns_postgres(
852 DatabaseBackend::Postgres,
853 vec!["email", "password"]
854 )]
855 #[case::add_unnamed_index_multiple_columns_mysql(
856 DatabaseBackend::MySql,
857 vec!["email", "password"]
858 )]
859 #[case::add_unnamed_index_multiple_columns_sqlite(
860 DatabaseBackend::Sqlite,
861 vec!["email", "password"]
862 )]
863 fn test_add_unnamed_index(#[case] backend: DatabaseBackend, #[case] columns: Vec<&str>) {
864 let action = MigrationAction::AddConstraint {
866 table: "user".into(),
867 constraint: TableConstraint::Index {
868 name: None,
869 columns: columns.iter().map(|s| s.to_string()).collect(),
870 },
871 };
872 let result = build_action_queries(&backend, &action, &[]).unwrap();
873 let sql = result[0].build(backend);
874
875 let expected_name = format!("ix_user__{}", columns.join("_"));
877 assert!(
878 sql.contains(&expected_name),
879 "Expected index name '{}' in SQL: {}",
880 expected_name,
881 sql
882 );
883
884 with_settings!({ snapshot_suffix => format!("add_unnamed_index_{}_{:?}", columns.join("_"), backend) }, {
885 assert_snapshot!(sql);
886 });
887 }
888
889 #[rstest]
890 #[case::remove_index_with_custom_name_postgres(
891 DatabaseBackend::Postgres,
892 "hello",
893 vec!["email", "password"]
894 )]
895 #[case::remove_index_with_custom_name_mysql(
896 DatabaseBackend::MySql,
897 "hello",
898 vec!["email", "password"]
899 )]
900 #[case::remove_index_with_custom_name_sqlite(
901 DatabaseBackend::Sqlite,
902 "hello",
903 vec!["email", "password"]
904 )]
905 fn test_remove_index_with_custom_name(
906 #[case] backend: DatabaseBackend,
907 #[case] index_name: &str,
908 #[case] columns: Vec<&str>,
909 ) {
910 let action = MigrationAction::RemoveConstraint {
912 table: "user".into(),
913 constraint: TableConstraint::Index {
914 name: Some(index_name.into()),
915 columns: columns.iter().map(|s| s.to_string()).collect(),
916 },
917 };
918 let result = build_action_queries(&backend, &action, &[]).unwrap();
919 let sql = result[0].build(backend);
920
921 let expected_name = format!("ix_user__{}", index_name);
923 assert!(
924 sql.contains(&expected_name),
925 "Expected index name '{}' in SQL: {}",
926 expected_name,
927 sql
928 );
929
930 with_settings!({ snapshot_suffix => format!("remove_index_custom_{}_{:?}", index_name, backend) }, {
931 assert_snapshot!(sql);
932 });
933 }
934
935 #[rstest]
936 #[case::remove_unnamed_index_single_column_postgres(
937 DatabaseBackend::Postgres,
938 vec!["email"]
939 )]
940 #[case::remove_unnamed_index_single_column_mysql(
941 DatabaseBackend::MySql,
942 vec!["email"]
943 )]
944 #[case::remove_unnamed_index_single_column_sqlite(
945 DatabaseBackend::Sqlite,
946 vec!["email"]
947 )]
948 #[case::remove_unnamed_index_multiple_columns_postgres(
949 DatabaseBackend::Postgres,
950 vec!["email", "password"]
951 )]
952 #[case::remove_unnamed_index_multiple_columns_mysql(
953 DatabaseBackend::MySql,
954 vec!["email", "password"]
955 )]
956 #[case::remove_unnamed_index_multiple_columns_sqlite(
957 DatabaseBackend::Sqlite,
958 vec!["email", "password"]
959 )]
960 fn test_remove_unnamed_index(#[case] backend: DatabaseBackend, #[case] columns: Vec<&str>) {
961 let action = MigrationAction::RemoveConstraint {
963 table: "user".into(),
964 constraint: TableConstraint::Index {
965 name: None,
966 columns: columns.iter().map(|s| s.to_string()).collect(),
967 },
968 };
969 let result = build_action_queries(&backend, &action, &[]).unwrap();
970 let sql = result[0].build(backend);
971
972 let expected_name = format!("ix_user__{}", columns.join("_"));
974 assert!(
975 sql.contains(&expected_name),
976 "Expected index name '{}' in SQL: {}",
977 expected_name,
978 sql
979 );
980
981 with_settings!({ snapshot_suffix => format!("remove_unnamed_index_{}_{:?}", columns.join("_"), backend) }, {
982 assert_snapshot!(sql);
983 });
984 }
985
986 #[rstest]
988 #[case::add_unique_with_custom_name_postgres(
989 DatabaseBackend::Postgres,
990 "email_unique",
991 vec!["email"]
992 )]
993 #[case::add_unique_with_custom_name_mysql(
994 DatabaseBackend::MySql,
995 "email_unique",
996 vec!["email"]
997 )]
998 #[case::add_unique_with_custom_name_sqlite(
999 DatabaseBackend::Sqlite,
1000 "email_unique",
1001 vec!["email"]
1002 )]
1003 fn test_add_unique_with_custom_name(
1004 #[case] backend: DatabaseBackend,
1005 #[case] constraint_name: &str,
1006 #[case] columns: Vec<&str>,
1007 ) {
1008 let action = MigrationAction::AddConstraint {
1010 table: "user".into(),
1011 constraint: TableConstraint::Unique {
1012 name: Some(constraint_name.into()),
1013 columns: columns.iter().map(|s| s.to_string()).collect(),
1014 },
1015 };
1016
1017 let current_schema = vec![TableDef {
1018 name: "user".into(),
1019 description: None,
1020 columns: vec![ColumnDef {
1021 name: "email".into(),
1022 r#type: ColumnType::Simple(SimpleColumnType::Text),
1023 nullable: true,
1024 default: None,
1025 comment: None,
1026 primary_key: None,
1027 unique: None,
1028 index: None,
1029 foreign_key: None,
1030 }],
1031 constraints: vec![],
1032 }];
1033
1034 let result = build_action_queries(&backend, &action, ¤t_schema).unwrap();
1035 let sql = result
1036 .iter()
1037 .map(|q| q.build(backend))
1038 .collect::<Vec<String>>()
1039 .join("\n");
1040
1041 let expected_name = format!("uq_user__{}", constraint_name);
1043 assert!(
1044 sql.contains(&expected_name),
1045 "Expected unique constraint name '{}' in SQL: {}",
1046 expected_name,
1047 sql
1048 );
1049
1050 with_settings!({ snapshot_suffix => format!("add_unique_custom_{}_{:?}", constraint_name, backend) }, {
1051 assert_snapshot!(sql);
1052 });
1053 }
1054
1055 #[rstest]
1056 #[case::add_unnamed_unique_single_column_postgres(
1057 DatabaseBackend::Postgres,
1058 vec!["email"]
1059 )]
1060 #[case::add_unnamed_unique_single_column_mysql(
1061 DatabaseBackend::MySql,
1062 vec!["email"]
1063 )]
1064 #[case::add_unnamed_unique_single_column_sqlite(
1065 DatabaseBackend::Sqlite,
1066 vec!["email"]
1067 )]
1068 #[case::add_unnamed_unique_multiple_columns_postgres(
1069 DatabaseBackend::Postgres,
1070 vec!["email", "username"]
1071 )]
1072 #[case::add_unnamed_unique_multiple_columns_mysql(
1073 DatabaseBackend::MySql,
1074 vec!["email", "username"]
1075 )]
1076 #[case::add_unnamed_unique_multiple_columns_sqlite(
1077 DatabaseBackend::Sqlite,
1078 vec!["email", "username"]
1079 )]
1080 fn test_add_unnamed_unique(#[case] backend: DatabaseBackend, #[case] columns: Vec<&str>) {
1081 let action = MigrationAction::AddConstraint {
1083 table: "user".into(),
1084 constraint: TableConstraint::Unique {
1085 name: None,
1086 columns: columns.iter().map(|s| s.to_string()).collect(),
1087 },
1088 };
1089
1090 let schema_columns: Vec<ColumnDef> = columns
1091 .iter()
1092 .map(|col| ColumnDef {
1093 name: col.to_string(),
1094 r#type: ColumnType::Simple(SimpleColumnType::Text),
1095 nullable: true,
1096 default: None,
1097 comment: None,
1098 primary_key: None,
1099 unique: None,
1100 index: None,
1101 foreign_key: None,
1102 })
1103 .collect();
1104
1105 let current_schema = vec![TableDef {
1106 name: "user".into(),
1107 description: None,
1108 columns: schema_columns,
1109 constraints: vec![],
1110 }];
1111
1112 let result = build_action_queries(&backend, &action, ¤t_schema).unwrap();
1113 let sql = result
1114 .iter()
1115 .map(|q| q.build(backend))
1116 .collect::<Vec<String>>()
1117 .join("\n");
1118
1119 let expected_name = format!("uq_user__{}", columns.join("_"));
1121 assert!(
1122 sql.contains(&expected_name),
1123 "Expected unique constraint name '{}' in SQL: {}",
1124 expected_name,
1125 sql
1126 );
1127
1128 with_settings!({ snapshot_suffix => format!("add_unnamed_unique_{}_{:?}", columns.join("_"), backend) }, {
1129 assert_snapshot!(sql);
1130 });
1131 }
1132
1133 #[rstest]
1134 #[case::remove_unique_with_custom_name_postgres(
1135 DatabaseBackend::Postgres,
1136 "email_unique",
1137 vec!["email"]
1138 )]
1139 #[case::remove_unique_with_custom_name_mysql(
1140 DatabaseBackend::MySql,
1141 "email_unique",
1142 vec!["email"]
1143 )]
1144 #[case::remove_unique_with_custom_name_sqlite(
1145 DatabaseBackend::Sqlite,
1146 "email_unique",
1147 vec!["email"]
1148 )]
1149 fn test_remove_unique_with_custom_name(
1150 #[case] backend: DatabaseBackend,
1151 #[case] constraint_name: &str,
1152 #[case] columns: Vec<&str>,
1153 ) {
1154 let constraint = TableConstraint::Unique {
1156 name: Some(constraint_name.into()),
1157 columns: columns.iter().map(|s| s.to_string()).collect(),
1158 };
1159
1160 let current_schema = vec![TableDef {
1161 name: "user".into(),
1162 description: None,
1163 columns: vec![ColumnDef {
1164 name: "email".into(),
1165 r#type: ColumnType::Simple(SimpleColumnType::Text),
1166 nullable: true,
1167 default: None,
1168 comment: None,
1169 primary_key: None,
1170 unique: None,
1171 index: None,
1172 foreign_key: None,
1173 }],
1174 constraints: vec![constraint.clone()],
1175 }];
1176
1177 let action = MigrationAction::RemoveConstraint {
1178 table: "user".into(),
1179 constraint,
1180 };
1181
1182 let result = build_action_queries(&backend, &action, ¤t_schema).unwrap();
1183 let sql = result
1184 .iter()
1185 .map(|q| q.build(backend))
1186 .collect::<Vec<String>>()
1187 .join("\n");
1188
1189 if backend != DatabaseBackend::Sqlite {
1191 let expected_name = format!("uq_user__{}", constraint_name);
1192 assert!(
1193 sql.contains(&expected_name),
1194 "Expected unique constraint name '{}' in SQL: {}",
1195 expected_name,
1196 sql
1197 );
1198 }
1199
1200 with_settings!({ snapshot_suffix => format!("remove_unique_custom_{}_{:?}", constraint_name, backend) }, {
1201 assert_snapshot!(sql);
1202 });
1203 }
1204
1205 #[rstest]
1206 #[case::remove_unnamed_unique_single_column_postgres(
1207 DatabaseBackend::Postgres,
1208 vec!["email"]
1209 )]
1210 #[case::remove_unnamed_unique_single_column_mysql(
1211 DatabaseBackend::MySql,
1212 vec!["email"]
1213 )]
1214 #[case::remove_unnamed_unique_single_column_sqlite(
1215 DatabaseBackend::Sqlite,
1216 vec!["email"]
1217 )]
1218 #[case::remove_unnamed_unique_multiple_columns_postgres(
1219 DatabaseBackend::Postgres,
1220 vec!["email", "username"]
1221 )]
1222 #[case::remove_unnamed_unique_multiple_columns_mysql(
1223 DatabaseBackend::MySql,
1224 vec!["email", "username"]
1225 )]
1226 #[case::remove_unnamed_unique_multiple_columns_sqlite(
1227 DatabaseBackend::Sqlite,
1228 vec!["email", "username"]
1229 )]
1230 fn test_remove_unnamed_unique(#[case] backend: DatabaseBackend, #[case] columns: Vec<&str>) {
1231 let constraint = TableConstraint::Unique {
1233 name: None,
1234 columns: columns.iter().map(|s| s.to_string()).collect(),
1235 };
1236
1237 let schema_columns: Vec<ColumnDef> = columns
1238 .iter()
1239 .map(|col| ColumnDef {
1240 name: col.to_string(),
1241 r#type: ColumnType::Simple(SimpleColumnType::Text),
1242 nullable: true,
1243 default: None,
1244 comment: None,
1245 primary_key: None,
1246 unique: None,
1247 index: None,
1248 foreign_key: None,
1249 })
1250 .collect();
1251
1252 let current_schema = vec![TableDef {
1253 name: "user".into(),
1254 description: None,
1255 columns: schema_columns,
1256 constraints: vec![constraint.clone()],
1257 }];
1258
1259 let action = MigrationAction::RemoveConstraint {
1260 table: "user".into(),
1261 constraint,
1262 };
1263
1264 let result = build_action_queries(&backend, &action, ¤t_schema).unwrap();
1265 let sql = result
1266 .iter()
1267 .map(|q| q.build(backend))
1268 .collect::<Vec<String>>()
1269 .join("\n");
1270
1271 if backend != DatabaseBackend::Sqlite {
1273 let expected_name = format!("uq_user__{}", columns.join("_"));
1274 assert!(
1275 sql.contains(&expected_name),
1276 "Expected unique constraint name '{}' in SQL: {}",
1277 expected_name,
1278 sql
1279 );
1280 }
1281
1282 with_settings!({ snapshot_suffix => format!("remove_unnamed_unique_{}_{:?}", columns.join("_"), backend) }, {
1283 assert_snapshot!(sql);
1284 });
1285 }
1286
1287 #[rstest]
1289 #[case::postgres_modify_nullable(DatabaseBackend::Postgres)]
1290 #[case::mysql_modify_nullable(DatabaseBackend::MySql)]
1291 #[case::sqlite_modify_nullable(DatabaseBackend::Sqlite)]
1292 fn test_build_action_queries_modify_column_nullable(#[case] backend: DatabaseBackend) {
1293 let action = MigrationAction::ModifyColumnNullable {
1294 table: "users".into(),
1295 column: "email".into(),
1296 nullable: false,
1297 fill_with: Some("'unknown'".into()),
1298 delete_null_rows: None,
1299 };
1300 let current_schema = vec![TableDef {
1301 name: "users".into(),
1302 description: None,
1303 columns: vec![ColumnDef {
1304 name: "email".into(),
1305 r#type: ColumnType::Simple(SimpleColumnType::Text),
1306 nullable: true,
1307 default: None,
1308 comment: None,
1309 primary_key: None,
1310 unique: None,
1311 index: None,
1312 foreign_key: None,
1313 }],
1314 constraints: vec![],
1315 }];
1316 let result = build_action_queries(&backend, &action, ¤t_schema).unwrap();
1317 assert!(!result.is_empty());
1318 let sql = result
1319 .iter()
1320 .map(|q| q.build(backend))
1321 .collect::<Vec<String>>()
1322 .join("\n");
1323
1324 assert!(sql.contains("UPDATE"));
1326 assert!(sql.contains("unknown"));
1327
1328 let suffix = format!(
1329 "{}_modify_nullable",
1330 match backend {
1331 DatabaseBackend::Postgres => "postgres",
1332 DatabaseBackend::MySql => "mysql",
1333 DatabaseBackend::Sqlite => "sqlite",
1334 }
1335 );
1336
1337 with_settings!({ snapshot_suffix => suffix }, {
1338 assert_snapshot!(sql);
1339 });
1340 }
1341
1342 #[rstest]
1344 #[case::postgres_modify_default(DatabaseBackend::Postgres)]
1345 #[case::mysql_modify_default(DatabaseBackend::MySql)]
1346 #[case::sqlite_modify_default(DatabaseBackend::Sqlite)]
1347 fn test_build_action_queries_modify_column_default(#[case] backend: DatabaseBackend) {
1348 let action = MigrationAction::ModifyColumnDefault {
1349 table: "users".into(),
1350 column: "status".into(),
1351 new_default: Some("'active'".into()),
1352 };
1353 let current_schema = vec![TableDef {
1354 name: "users".into(),
1355 description: None,
1356 columns: vec![ColumnDef {
1357 name: "status".into(),
1358 r#type: ColumnType::Simple(SimpleColumnType::Text),
1359 nullable: true,
1360 default: None,
1361 comment: None,
1362 primary_key: None,
1363 unique: None,
1364 index: None,
1365 foreign_key: None,
1366 }],
1367 constraints: vec![],
1368 }];
1369 let result = build_action_queries(&backend, &action, ¤t_schema).unwrap();
1370 assert!(!result.is_empty());
1371 let sql = result
1372 .iter()
1373 .map(|q| q.build(backend))
1374 .collect::<Vec<String>>()
1375 .join("\n");
1376
1377 assert!(sql.contains("DEFAULT") || sql.contains("active"));
1379
1380 let suffix = format!(
1381 "{}_modify_default",
1382 match backend {
1383 DatabaseBackend::Postgres => "postgres",
1384 DatabaseBackend::MySql => "mysql",
1385 DatabaseBackend::Sqlite => "sqlite",
1386 }
1387 );
1388
1389 with_settings!({ snapshot_suffix => suffix }, {
1390 assert_snapshot!(sql);
1391 });
1392 }
1393
1394 #[rstest]
1396 #[case::postgres_modify_comment(DatabaseBackend::Postgres)]
1397 #[case::mysql_modify_comment(DatabaseBackend::MySql)]
1398 #[case::sqlite_modify_comment(DatabaseBackend::Sqlite)]
1399 fn test_build_action_queries_modify_column_comment(#[case] backend: DatabaseBackend) {
1400 let action = MigrationAction::ModifyColumnComment {
1401 table: "users".into(),
1402 column: "email".into(),
1403 new_comment: Some("User email address".into()),
1404 };
1405 let current_schema = vec![TableDef {
1406 name: "users".into(),
1407 description: None,
1408 columns: vec![ColumnDef {
1409 name: "email".into(),
1410 r#type: ColumnType::Simple(SimpleColumnType::Text),
1411 nullable: true,
1412 default: None,
1413 comment: None,
1414 primary_key: None,
1415 unique: None,
1416 index: None,
1417 foreign_key: None,
1418 }],
1419 constraints: vec![],
1420 }];
1421 let result = build_action_queries(&backend, &action, ¤t_schema).unwrap();
1422 let sql = result
1423 .iter()
1424 .map(|q| q.build(backend))
1425 .collect::<Vec<String>>()
1426 .join("\n");
1427
1428 if backend != DatabaseBackend::Sqlite {
1430 assert!(sql.contains("COMMENT") || sql.contains("User email address"));
1431 }
1432
1433 let suffix = format!(
1434 "{}_modify_comment",
1435 match backend {
1436 DatabaseBackend::Postgres => "postgres",
1437 DatabaseBackend::MySql => "mysql",
1438 DatabaseBackend::Sqlite => "sqlite",
1439 }
1440 );
1441
1442 with_settings!({ snapshot_suffix => suffix }, {
1443 assert_snapshot!(sql);
1444 });
1445 }
1446
1447 #[rstest]
1448 #[case::create_table_func_default_postgres(DatabaseBackend::Postgres)]
1449 #[case::create_table_func_default_mysql(DatabaseBackend::MySql)]
1450 #[case::create_table_func_default_sqlite(DatabaseBackend::Sqlite)]
1451 fn test_create_table_with_function_default(#[case] backend: DatabaseBackend) {
1452 let action = MigrationAction::CreateTable {
1455 table: "users".into(),
1456 columns: vec![
1457 ColumnDef {
1458 name: "id".into(),
1459 r#type: ColumnType::Simple(SimpleColumnType::Uuid),
1460 nullable: false,
1461 default: Some("gen_random_uuid()".into()),
1462 comment: None,
1463 primary_key: None,
1464 unique: None,
1465 index: None,
1466 foreign_key: None,
1467 },
1468 ColumnDef {
1469 name: "created_at".into(),
1470 r#type: ColumnType::Simple(SimpleColumnType::Timestamptz),
1471 nullable: false,
1472 default: Some("now()".into()),
1473 comment: None,
1474 primary_key: None,
1475 unique: None,
1476 index: None,
1477 foreign_key: None,
1478 },
1479 ],
1480 constraints: vec![],
1481 };
1482 let result = build_action_queries(&backend, &action, &[]).unwrap();
1483 let sql = result
1484 .iter()
1485 .map(|q| q.build(backend))
1486 .collect::<Vec<_>>()
1487 .join(";\n");
1488
1489 with_settings!({ snapshot_suffix => format!("create_table_func_default_{:?}", backend) }, {
1490 assert_snapshot!(sql);
1491 });
1492 }
1493
1494 #[rstest]
1495 #[case::delete_enum_column_postgres(DatabaseBackend::Postgres)]
1496 #[case::delete_enum_column_mysql(DatabaseBackend::MySql)]
1497 #[case::delete_enum_column_sqlite(DatabaseBackend::Sqlite)]
1498 fn test_delete_column_with_enum_type(#[case] backend: DatabaseBackend) {
1499 let action = MigrationAction::DeleteColumn {
1502 table: "orders".into(),
1503 column: "status".into(),
1504 };
1505 let schema = vec![TableDef {
1506 name: "orders".into(),
1507 description: None,
1508 columns: vec![
1509 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1510 ColumnDef {
1511 name: "status".into(),
1512 r#type: ColumnType::Complex(vespertide_core::ComplexColumnType::Enum {
1513 name: "order_status".into(),
1514 values: vespertide_core::EnumValues::String(vec![
1515 "pending".into(),
1516 "shipped".into(),
1517 ]),
1518 }),
1519 nullable: false,
1520 default: None,
1521 comment: None,
1522 primary_key: None,
1523 unique: None,
1524 index: None,
1525 foreign_key: None,
1526 },
1527 ],
1528 constraints: vec![],
1529 }];
1530 let result = build_action_queries(&backend, &action, &schema).unwrap();
1531 let sql = result
1532 .iter()
1533 .map(|q| q.build(backend))
1534 .collect::<Vec<_>>()
1535 .join(";\n");
1536
1537 with_settings!({ snapshot_suffix => format!("delete_enum_column_{:?}", backend) }, {
1538 assert_snapshot!(sql);
1539 });
1540 }
1541
1542 #[rstest]
1543 #[case::replace_fk_constraint_postgres(DatabaseBackend::Postgres)]
1544 #[case::replace_fk_constraint_mysql(DatabaseBackend::MySql)]
1545 #[case::replace_fk_constraint_sqlite(DatabaseBackend::Sqlite)]
1546 fn test_replace_fk_constraint(#[case] backend: DatabaseBackend) {
1547 let schema = vec![TableDef {
1548 name: "posts".into(),
1549 description: None,
1550 columns: vec![
1551 col("id", ColumnType::Simple(SimpleColumnType::Integer)),
1552 col("user_id", ColumnType::Simple(SimpleColumnType::Integer)),
1553 ],
1554 constraints: vec![
1555 TableConstraint::PrimaryKey {
1556 auto_increment: false,
1557 columns: vec!["id".into()],
1558 },
1559 TableConstraint::ForeignKey {
1560 name: Some("fk_user".into()),
1561 columns: vec!["user_id".into()],
1562 ref_table: "users".into(),
1563 ref_columns: vec!["id".into()],
1564 on_delete: None,
1565 on_update: None,
1566 },
1567 ],
1568 }];
1569 let action = MigrationAction::ReplaceConstraint {
1570 table: "posts".into(),
1571 from: TableConstraint::ForeignKey {
1572 name: Some("fk_user".into()),
1573 columns: vec!["user_id".into()],
1574 ref_table: "users".into(),
1575 ref_columns: vec!["id".into()],
1576 on_delete: None,
1577 on_update: None,
1578 },
1579 to: TableConstraint::ForeignKey {
1580 name: Some("fk_user".into()),
1581 columns: vec!["user_id".into()],
1582 ref_table: "users".into(),
1583 ref_columns: vec!["id".into()],
1584 on_delete: Some(ReferenceAction::Cascade),
1585 on_update: None,
1586 },
1587 };
1588 let result = build_action_queries(&backend, &action, &schema).unwrap();
1589 let sql = result
1590 .iter()
1591 .map(|q| q.build(backend))
1592 .collect::<Vec<_>>()
1593 .join(";\n");
1594
1595 let suffix = format!(
1596 "replace_fk_constraint_{}",
1597 match backend {
1598 DatabaseBackend::Postgres => "postgres",
1599 DatabaseBackend::MySql => "mysql",
1600 DatabaseBackend::Sqlite => "sqlite",
1601 }
1602 );
1603
1604 with_settings!({ snapshot_suffix => suffix }, {
1605 assert_snapshot!(sql);
1606 });
1607 }
1608}