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