1use super::Operation;
4use super::dependency::{OptionalDependency, SwappableDependency};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Default, Serialize, Deserialize)]
9pub struct Migration {
10 pub name: String,
12
13 pub app_label: String,
15
16 pub operations: Vec<Operation>,
18
19 pub dependencies: Vec<(String, String)>,
21
22 pub replaces: Vec<(String, String)>,
24
25 pub atomic: bool,
27
28 pub initial: Option<bool>,
33
34 #[serde(default)]
37 pub state_only: bool,
38
39 #[serde(default)]
42 pub database_only: bool,
43
44 #[serde(default)]
54 pub swappable_dependencies: Vec<SwappableDependency>,
55
56 #[serde(default)]
70 pub optional_dependencies: Vec<OptionalDependency>,
71}
72
73impl Migration {
74 pub fn new(name: impl Into<String>, app_label: impl Into<String>) -> Self {
87 Self {
88 name: name.into(),
89 app_label: app_label.into(),
90 operations: Vec::new(),
91 dependencies: Vec::new(),
92 replaces: Vec::new(),
93 atomic: true,
94 initial: None,
95 state_only: false,
96 database_only: false,
97 swappable_dependencies: Vec::new(),
98 optional_dependencies: Vec::new(),
99 }
100 }
101 pub fn add_operation(mut self, operation: Operation) -> Self {
121 self.operations.push(operation);
122 self
123 }
124 pub fn add_dependency(mut self, app_label: impl Into<String>, name: impl Into<String>) -> Self {
139 self.dependencies.push((app_label.into(), name.into()));
140 self
141 }
142
143 pub fn add_swappable_dependency(mut self, dependency: SwappableDependency) -> Self {
165 self.swappable_dependencies.push(dependency);
166 self
167 }
168
169 pub fn add_optional_dependency(mut self, dependency: OptionalDependency) -> Self {
190 self.optional_dependencies.push(dependency);
191 self
192 }
193
194 pub fn atomic(mut self, atomic: bool) -> Self {
207 self.atomic = atomic;
208 self
209 }
210 pub fn id(&self) -> String {
221 format!("{}.{}", self.app_label, self.name)
222 }
223
224 pub fn initial(mut self, initial: bool) -> Self {
237 self.initial = Some(initial);
238 self
239 }
240
241 pub fn state_only(mut self, value: bool) -> Self {
255 self.state_only = value;
256 self
257 }
258
259 pub fn database_only(mut self, value: bool) -> Self {
273 self.database_only = value;
274 self
275 }
276
277 pub fn is_initial(&self) -> bool {
308 match self.initial {
309 Some(true) => true,
310 Some(false) => false,
311 None => self.dependencies.is_empty(),
312 }
313 }
314}
315
316#[cfg(test)]
321mod migrations_extended_tests {
322 use crate::migrations::operations;
323 use crate::migrations::{FieldType, ForeignKeyAction};
324
325 #[test]
326 fn test_add_alter_order_with_respect_to() {
328 use crate::migrations::ProjectState;
329 use crate::migrations::operations::*;
330
331 let mut state = ProjectState::new();
332
333 let create_categories = Operation::CreateTable {
335 name: "categories".to_string(),
336 columns: vec![
337 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
338 ColumnDefinition::new("name", FieldType::VarChar(100)),
339 ],
340 constraints: vec![],
341 without_rowid: None,
342 partition: None,
343 interleave_in_parent: None,
344 };
345 create_categories.state_forwards("testapp", &mut state);
346
347 let create_items = Operation::CreateTable {
349 name: "items".to_string(),
350 columns: vec![
351 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
352 ColumnDefinition::new("name", FieldType::VarChar(200)),
353 ColumnDefinition::new(
354 "category_id",
355 FieldType::Custom("INTEGER REFERENCES categories(id)".to_string()),
356 ),
357 ],
358 constraints: vec![],
359 without_rowid: None,
360 partition: None,
361 interleave_in_parent: None,
362 };
363 create_items.state_forwards("testapp", &mut state);
364
365 let add_order = Operation::AddColumn {
367 table: "items".to_string(),
368 column: ColumnDefinition::new(
369 "_order",
370 FieldType::Custom("INTEGER NOT NULL DEFAULT 0".to_string()),
371 ),
372 mysql_options: None,
373 };
374 add_order.state_forwards("testapp", &mut state);
375
376 let _create_index = Operation::CreateIndex {
378 table: "items".to_string(),
379 columns: vec!["category_id".to_string(), "_order".to_string()],
380 unique: false,
381 index_type: None,
382 where_clause: None,
383 concurrently: false,
384 expressions: None,
385 mysql_options: None,
386 operator_class: None,
387 };
388
389 let model = state.get_model("testapp", "items").unwrap();
390 assert!(model.fields.contains_key("_order"));
391 assert!(model.fields.contains_key("category_id"));
392 }
393
394 #[test]
395 fn test_add_alter_order_with_respect_to_1() {
397 use crate::migrations::ProjectState;
398 use crate::migrations::operations::*;
399
400 let mut state = ProjectState::new();
401
402 let create_parent = Operation::CreateTable {
404 name: "authors".to_string(),
405 columns: vec![ColumnDefinition::new(
406 "id",
407 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
408 )],
409 constraints: vec![],
410 without_rowid: None,
411 partition: None,
412 interleave_in_parent: None,
413 };
414 create_parent.state_forwards("app", &mut state);
415
416 let create_child = Operation::CreateTable {
418 name: "books".to_string(),
419 columns: vec![
420 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
421 ColumnDefinition::new("title", FieldType::VarChar(255)),
422 ColumnDefinition::new(
423 "author_id",
424 FieldType::Custom("INTEGER REFERENCES authors(id)".to_string()),
425 ),
426 ],
427 constraints: vec![],
428 without_rowid: None,
429 partition: None,
430 interleave_in_parent: None,
431 };
432 create_child.state_forwards("app", &mut state);
433
434 let add_order = Operation::AddColumn {
436 table: "books".to_string(),
437 column: ColumnDefinition::new(
438 "_order",
439 FieldType::Custom("INTEGER NOT NULL DEFAULT 0".to_string()),
440 ),
441 mysql_options: None,
442 };
443 add_order.state_forwards("app", &mut state);
444
445 assert!(
446 state
447 .get_model("app", "books")
448 .unwrap()
449 .fields
450 .contains_key("_order")
451 );
452 }
453
454 #[test]
455 fn test_add_auto_field_does_not_request_default() {
457 use crate::migrations::ProjectState;
458 use crate::migrations::operations::*;
459
460 let mut state = ProjectState::new();
461
462 let create_op = Operation::CreateTable {
463 name: "items".to_string(),
464 columns: vec![ColumnDefinition::new("name", FieldType::VarChar(255))],
465 constraints: vec![],
466 without_rowid: None,
467 partition: None,
468 interleave_in_parent: None,
469 };
470 create_op.state_forwards("testapp", &mut state);
471
472 let add_op = Operation::AddColumn {
474 table: "items".to_string(),
475 column: ColumnDefinition::new(
476 "id",
477 FieldType::Custom("INTEGER PRIMARY KEY AUTOINCREMENT".to_string()),
478 ),
479 mysql_options: None,
480 };
481 add_op.state_forwards("testapp", &mut state);
482
483 assert!(
484 state
485 .get_model("testapp", "items")
486 .unwrap()
487 .fields
488 .contains_key("id")
489 );
490 }
491
492 #[test]
493 fn test_add_auto_field_does_not_request_default_1() {
495 use crate::migrations::ProjectState;
496 use crate::migrations::operations::*;
497
498 let mut state = ProjectState::new();
499
500 let create_op = Operation::CreateTable {
501 name: "entries".to_string(),
502 columns: vec![ColumnDefinition::new("title", FieldType::Text)],
503 constraints: vec![],
504 without_rowid: None,
505 partition: None,
506 interleave_in_parent: None,
507 };
508 create_op.state_forwards("app", &mut state);
509
510 let add_op = Operation::AddColumn {
511 table: "entries".to_string(),
512 column: ColumnDefinition::new(
513 "entry_id",
514 FieldType::Custom("SERIAL PRIMARY KEY".to_string()),
515 ),
516 mysql_options: None,
517 };
518 add_op.state_forwards("app", &mut state);
519
520 assert!(state.get_model("app", "entries").is_some());
521 }
522
523 #[test]
524 fn test_add_blank_textfield_and_charfield() {
526 use crate::migrations::ProjectState;
527 use crate::migrations::operations::*;
528
529 let mut state = ProjectState::new();
530
531 let create_op = Operation::CreateTable {
532 name: "articles".to_string(),
533 columns: vec![ColumnDefinition::new(
534 "id",
535 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
536 )],
537 constraints: vec![],
538 without_rowid: None,
539 partition: None,
540 interleave_in_parent: None,
541 };
542 create_op.state_forwards("testapp", &mut state);
543
544 let add_text = Operation::AddColumn {
546 table: "articles".to_string(),
547 column: ColumnDefinition::new("content", FieldType::Text),
548 mysql_options: None,
549 };
550 add_text.state_forwards("testapp", &mut state);
551
552 let add_char = Operation::AddColumn {
553 table: "articles".to_string(),
554 column: ColumnDefinition::new("title", FieldType::VarChar(255)),
555 mysql_options: None,
556 };
557 add_char.state_forwards("testapp", &mut state);
558
559 let model = state.get_model("testapp", "articles").unwrap();
560 assert!(model.fields.contains_key("content"));
561 assert!(model.fields.contains_key("title"));
562 }
563
564 #[test]
565 fn test_add_blank_textfield_and_charfield_1() {
567 use crate::migrations::ProjectState;
568 use crate::migrations::operations::*;
569
570 let mut state = ProjectState::new();
571
572 let create_op = Operation::CreateTable {
573 name: "posts".to_string(),
574 columns: vec![ColumnDefinition::new(
575 "id",
576 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
577 )],
578 constraints: vec![],
579 without_rowid: None,
580 partition: None,
581 interleave_in_parent: None,
582 };
583 create_op.state_forwards("app", &mut state);
584
585 let add_op = Operation::AddColumn {
586 table: "posts".to_string(),
587 column: ColumnDefinition::new(
588 "description",
589 FieldType::Custom("TEXT NULL".to_string()),
590 ),
591 mysql_options: None,
592 };
593 add_op.state_forwards("app", &mut state);
594
595 assert!(
596 state
597 .get_model("app", "posts")
598 .unwrap()
599 .fields
600 .contains_key("description")
601 );
602 }
603
604 #[test]
605 fn test_add_composite_pk() {
607 use crate::migrations::ProjectState;
608 use crate::migrations::operations::*;
609
610 let mut state = ProjectState::new();
611
612 let create_op = Operation::CreateTable {
615 name: "order_items".to_string(),
616 columns: vec![
617 ColumnDefinition::new("order_id", FieldType::Integer),
618 ColumnDefinition::new("product_id", FieldType::Integer),
619 ColumnDefinition::new("quantity", FieldType::Integer),
620 ],
621 constraints: vec![],
622 without_rowid: None,
623 partition: None,
624 interleave_in_parent: None,
625 };
626 create_op.state_forwards("testapp", &mut state);
627
628 let model = state.get_model("testapp", "order_items").unwrap();
629 assert!(model.fields.contains_key("order_id"));
630 assert!(model.fields.contains_key("product_id"));
631 }
632
633 #[test]
634 fn test_add_composite_pk_1() {
636 use crate::migrations::ProjectState;
637 use crate::migrations::operations::*;
638
639 let mut state = ProjectState::new();
640
641 let create_op = Operation::CreateTable {
643 name: "user_roles".to_string(),
644 columns: vec![
645 ColumnDefinition::new("user_id", FieldType::Integer),
646 ColumnDefinition::new("role_id", FieldType::Integer),
647 ],
648 constraints: vec![],
649 without_rowid: None,
650 partition: None,
651 interleave_in_parent: None,
652 };
653 create_op.state_forwards("app", &mut state);
654
655 assert!(state.get_model("app", "user_roles").is_some());
656 }
657
658 #[test]
659 fn test_add_constraints() {
661 use crate::migrations::operations::*;
662
663 let op = Operation::AddConstraint {
665 table: "users".to_string(),
666 constraint_sql: "CHECK (age >= 18)".to_string(),
667 };
668
669 let sql = op.to_sql(&SqlDialect::Postgres);
670 assert!(sql.contains("ALTER TABLE users"));
671 assert!(sql.contains("ADD CHECK (age >= 18)"));
672 }
673
674 #[test]
675 fn test_add_constraints_1() {
677 use crate::migrations::operations::*;
678
679 let op = Operation::AddConstraint {
681 table: "products".to_string(),
682 constraint_sql: "UNIQUE (sku)".to_string(),
683 };
684
685 let sql = op.to_sql(&SqlDialect::Postgres);
686 assert!(sql.contains("ALTER TABLE products"));
687 assert!(sql.contains("ADD UNIQUE (sku)"));
688 }
689
690 #[test]
691 fn test_add_constraints_with_dict_keys() {
693 use crate::migrations::ProjectState;
694 use crate::migrations::operations::*;
695
696 let mut state = ProjectState::new();
697
698 let create_op = Operation::CreateTable {
699 name: "products".to_string(),
700 columns: vec![
701 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
702 ColumnDefinition::new(
703 "price",
704 FieldType::Decimal {
705 precision: 10,
706 scale: 2,
707 },
708 ),
709 ColumnDefinition::new(
710 "discount_price",
711 FieldType::Decimal {
712 precision: 10,
713 scale: 2,
714 },
715 ),
716 ],
717 constraints: vec![
718 Constraint::Check {
719 name: "price_positive".to_string(),
720 expression: "price >= 0".to_string(),
721 },
722 Constraint::Check {
723 name: "discount_price_valid".to_string(),
724 expression: "discount_price <= price".to_string(),
725 },
726 ],
727 without_rowid: None,
728 partition: None,
729 interleave_in_parent: None,
730 };
731 create_op.state_forwards("testapp", &mut state);
732
733 let model = state.get_model("testapp", "products").unwrap();
734 assert!(model.fields.contains_key("price"));
735 assert!(model.fields.contains_key("discount_price"));
736 }
737
738 #[test]
739 fn test_add_constraints_with_dict_keys_1() {
741 use crate::migrations::ProjectState;
742 use crate::migrations::operations::*;
743
744 let mut state = ProjectState::new();
745
746 let create_op = Operation::CreateTable {
747 name: "users".to_string(),
748 columns: vec![
749 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
750 ColumnDefinition::new("age", FieldType::Integer),
751 ],
752 constraints: vec![Constraint::Check {
753 name: "age_valid_range".to_string(),
754 expression: "age >= 0 AND age <= 150".to_string(),
755 }],
756 without_rowid: None,
757 partition: None,
758 interleave_in_parent: None,
759 };
760 create_op.state_forwards("app", &mut state);
761
762 assert!(state.get_model("app", "users").is_some());
763 }
764
765 #[test]
766 fn test_add_constraints_with_new_model() {
768 use crate::migrations::ProjectState;
769 use crate::migrations::operations::*;
770
771 let mut state = ProjectState::new();
772
773 let create_op = Operation::CreateTable {
775 name: "users".to_string(),
776 columns: vec![
777 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
778 ColumnDefinition::new("age", FieldType::Integer),
779 ],
780 constraints: vec![Constraint::Check {
781 name: "age_adult".to_string(),
782 expression: "age >= 18".to_string(),
783 }],
784 without_rowid: None,
785 partition: None,
786 interleave_in_parent: None,
787 };
788 create_op.state_forwards("testapp", &mut state);
789
790 let model = state.get_model("testapp", "users").unwrap();
791 assert!(model.fields.contains_key("id"));
792 assert!(model.fields.contains_key("age"));
793 }
794
795 #[test]
796 fn test_add_constraints_with_new_model_1() {
798 use crate::migrations::ProjectState;
799 use crate::migrations::operations::*;
800
801 let mut state = ProjectState::new();
802
803 let create_op = Operation::CreateTable {
804 name: "products".to_string(),
805 columns: vec![
806 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
807 ColumnDefinition::new(
808 "price",
809 FieldType::Decimal {
810 precision: 10,
811 scale: 2,
812 },
813 ),
814 ],
815 constraints: vec![Constraint::Check {
816 name: "price_positive".to_string(),
817 expression: "price > 0".to_string(),
818 }],
819 without_rowid: None,
820 partition: None,
821 interleave_in_parent: None,
822 };
823 create_op.state_forwards("app", &mut state);
824
825 assert!(state.get_model("app", "products").is_some());
826 }
827
828 #[test]
829 fn test_add_custom_fk_with_hardcoded_to() {
831 use crate::migrations::ProjectState;
832 use crate::migrations::operations::*;
833
834 let mut state = ProjectState::new();
835
836 let create_users = Operation::CreateTable {
838 name: "users".to_string(),
839 columns: vec![ColumnDefinition::new(
840 "id",
841 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
842 )],
843 constraints: vec![],
844 without_rowid: None,
845 partition: None,
846 interleave_in_parent: None,
847 };
848 create_users.state_forwards("testapp", &mut state);
849
850 let create_posts = Operation::CreateTable {
852 name: "posts".to_string(),
853 columns: vec![
854 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
855 ColumnDefinition::new("author_id", FieldType::Integer),
856 ],
857 constraints: vec![Constraint::ForeignKey {
858 name: "fk_posts_author".to_string(),
859 columns: vec!["author_id".to_string()],
860 referenced_table: "users".to_string(),
861 referenced_columns: vec!["id".to_string()],
862 on_delete: ForeignKeyAction::Cascade,
863 on_update: ForeignKeyAction::Cascade,
864 deferrable: None,
865 }],
866 without_rowid: None,
867 interleave_in_parent: None,
868 partition: None,
869 };
870 create_posts.state_forwards("testapp", &mut state);
871
872 assert!(
873 state
874 .get_model("testapp", "posts")
875 .unwrap()
876 .fields
877 .contains_key("author_id")
878 );
879 }
880
881 #[test]
882 fn test_add_custom_fk_with_hardcoded_to_1() {
884 use crate::migrations::ProjectState;
885 use crate::migrations::operations::*;
886
887 let mut state = ProjectState::new();
888
889 let create_categories = Operation::CreateTable {
890 name: "categories".to_string(),
891 columns: vec![ColumnDefinition::new(
892 "id",
893 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
894 )],
895 constraints: vec![],
896 without_rowid: None,
897 partition: None,
898 interleave_in_parent: None,
899 };
900 create_categories.state_forwards("app", &mut state);
901
902 let create_products = Operation::CreateTable {
903 name: "products".to_string(),
904 columns: vec![
905 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
906 ColumnDefinition::new(
907 "category_id",
908 FieldType::Custom("INTEGER REFERENCES categories(id)".to_string()),
909 ),
910 ],
911 constraints: vec![],
912 without_rowid: None,
913 partition: None,
914 interleave_in_parent: None,
915 };
916 create_products.state_forwards("app", &mut state);
917
918 assert!(state.get_model("app", "products").is_some());
919 }
920
921 #[test]
922 fn test_add_date_fields_with_auto_now_add_asking_for_default() {
924 use crate::migrations::ProjectState;
925 use crate::migrations::operations::*;
926
927 let mut state = ProjectState::new();
928
929 let create_op = Operation::CreateTable {
930 name: "posts".to_string(),
931 columns: vec![ColumnDefinition::new(
932 "id",
933 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
934 )],
935 constraints: vec![],
936 without_rowid: None,
937 partition: None,
938 interleave_in_parent: None,
939 };
940 create_op.state_forwards("testapp", &mut state);
941
942 let add_op = Operation::AddColumn {
944 table: "posts".to_string(),
945 column: ColumnDefinition::new(
946 "created_at",
947 FieldType::Custom(
948 FieldType::Custom("TIMESTAMP DEFAULT CURRENT_TIMESTAMP".to_string())
949 .to_string(),
950 ),
951 ),
952 mysql_options: None,
953 };
954 add_op.state_forwards("testapp", &mut state);
955
956 assert!(
957 state
958 .get_model("testapp", "posts")
959 .unwrap()
960 .fields
961 .contains_key("created_at")
962 );
963 }
964
965 #[test]
966 fn test_add_date_fields_with_auto_now_add_asking_for_default_1() {
968 use crate::migrations::ProjectState;
969 use crate::migrations::operations::*;
970
971 let mut state = ProjectState::new();
972
973 let create_op = Operation::CreateTable {
974 name: "articles".to_string(),
975 columns: vec![ColumnDefinition::new(
976 "id",
977 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
978 )],
979 constraints: vec![],
980 without_rowid: None,
981 partition: None,
982 interleave_in_parent: None,
983 };
984 create_op.state_forwards("app", &mut state);
985
986 let add_op = Operation::AddColumn {
987 table: "articles".to_string(),
988 column: ColumnDefinition::new(
989 "published_at",
990 FieldType::Custom("TIMESTAMP DEFAULT NOW()".to_string()),
991 ),
992 mysql_options: None,
993 };
994 add_op.state_forwards("app", &mut state);
995
996 assert!(state.get_model("app", "articles").is_some());
997 }
998
999 #[test]
1000 fn test_add_date_fields_with_auto_now_add_not_asking_for_null_addition() {
1002 use crate::migrations::ProjectState;
1003 use crate::migrations::operations::*;
1004
1005 let mut state = ProjectState::new();
1006
1007 let create_op = Operation::CreateTable {
1008 name: "events".to_string(),
1009 columns: vec![ColumnDefinition::new(
1010 "id",
1011 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
1012 )],
1013 constraints: vec![],
1014 without_rowid: None,
1015 partition: None,
1016 interleave_in_parent: None,
1017 };
1018 create_op.state_forwards("testapp", &mut state);
1019
1020 let add_op = Operation::AddColumn {
1022 table: "events".to_string(),
1023 column: ColumnDefinition::new(
1024 "created_at",
1025 FieldType::Custom("TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP".to_string()),
1026 ),
1027 mysql_options: None,
1028 };
1029 add_op.state_forwards("testapp", &mut state);
1030
1031 assert!(
1032 state
1033 .get_model("testapp", "events")
1034 .unwrap()
1035 .fields
1036 .contains_key("created_at")
1037 );
1038 }
1039
1040 #[test]
1041 fn test_add_date_fields_with_auto_now_add_not_asking_for_null_addition_1() {
1043 use crate::migrations::ProjectState;
1044 use crate::migrations::operations::*;
1045
1046 let mut state = ProjectState::new();
1047
1048 let create_op = Operation::CreateTable {
1049 name: "logs".to_string(),
1050 columns: vec![ColumnDefinition::new(
1051 "id",
1052 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
1053 )],
1054 constraints: vec![],
1055 without_rowid: None,
1056 partition: None,
1057 interleave_in_parent: None,
1058 };
1059 create_op.state_forwards("app", &mut state);
1060
1061 let add_op = Operation::AddColumn {
1062 table: "logs".to_string(),
1063 column: ColumnDefinition::new(
1064 "timestamp",
1065 FieldType::Custom("DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP".to_string()),
1066 ),
1067 mysql_options: None,
1068 };
1069 add_op.state_forwards("app", &mut state);
1070
1071 assert!(state.get_model("app", "logs").is_some());
1072 }
1073
1074 #[test]
1075 fn test_add_date_fields_with_auto_now_not_asking_for_default() {
1077 use crate::migrations::ProjectState;
1078 use crate::migrations::operations::*;
1079
1080 let mut state = ProjectState::new();
1081
1082 let create_op = Operation::CreateTable {
1083 name: "records".to_string(),
1084 columns: vec![ColumnDefinition::new(
1085 "id",
1086 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
1087 )],
1088 constraints: vec![],
1089 without_rowid: None,
1090 partition: None,
1091 interleave_in_parent: None,
1092 };
1093 create_op.state_forwards("testapp", &mut state);
1094
1095 let add_op = Operation::AddColumn {
1098 table: "records".to_string(),
1099 column: ColumnDefinition::new("updated_at", FieldType::Custom("TIMESTAMP".to_string())),
1100 mysql_options: None,
1101 };
1102 add_op.state_forwards("testapp", &mut state);
1103
1104 assert!(
1105 state
1106 .get_model("testapp", "records")
1107 .unwrap()
1108 .fields
1109 .contains_key("updated_at")
1110 );
1111 }
1112
1113 #[test]
1114 fn test_add_date_fields_with_auto_now_not_asking_for_default_1() {
1116 use crate::migrations::ProjectState;
1117 use crate::migrations::operations::*;
1118
1119 let mut state = ProjectState::new();
1120
1121 let create_op = Operation::CreateTable {
1122 name: "profiles".to_string(),
1123 columns: vec![ColumnDefinition::new(
1124 "id",
1125 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
1126 )],
1127 constraints: vec![],
1128 without_rowid: None,
1129 partition: None,
1130 interleave_in_parent: None,
1131 };
1132 create_op.state_forwards("app", &mut state);
1133
1134 let add_op = Operation::AddColumn {
1135 table: "profiles".to_string(),
1136 column: ColumnDefinition::new("modified", FieldType::Custom("DATETIME".to_string())),
1137 mysql_options: None,
1138 };
1139 add_op.state_forwards("app", &mut state);
1140
1141 assert!(state.get_model("app", "profiles").is_some());
1142 }
1143
1144 #[test]
1145 fn test_add_field() {
1147 use crate::migrations::ProjectState;
1148 use crate::migrations::operations::*;
1149
1150 let mut state = ProjectState::new();
1151
1152 let create_op = Operation::CreateTable {
1154 name: "test_table".to_string(),
1155 columns: vec![ColumnDefinition::new(
1156 "id",
1157 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
1158 )],
1159 constraints: vec![],
1160 without_rowid: None,
1161 partition: None,
1162 interleave_in_parent: None,
1163 };
1164 create_op.state_forwards("testapp", &mut state);
1165
1166 let add_op = Operation::AddColumn {
1168 table: "test_table".to_string(),
1169 column: ColumnDefinition::new("name", FieldType::VarChar(255)),
1170 mysql_options: None,
1171 };
1172 add_op.state_forwards("testapp", &mut state);
1173
1174 let model = state.get_model("testapp", "test_table").unwrap();
1175 assert!(model.fields.contains_key("name"));
1176 }
1177
1178 #[test]
1179 fn test_add_field_1() {
1181 use crate::migrations::ProjectState;
1182 use crate::migrations::operations::*;
1183
1184 let mut state = ProjectState::new();
1185
1186 let create_op = Operation::CreateTable {
1187 name: "users".to_string(),
1188 columns: vec![ColumnDefinition::new(
1189 "id",
1190 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
1191 )],
1192 constraints: vec![],
1193 without_rowid: None,
1194 partition: None,
1195 interleave_in_parent: None,
1196 };
1197 create_op.state_forwards("app", &mut state);
1198
1199 let add_op = Operation::AddColumn {
1200 table: "users".to_string(),
1201 column: ColumnDefinition::new("email", FieldType::VarChar(255)),
1202 mysql_options: None,
1203 };
1204 add_op.state_forwards("app", &mut state);
1205
1206 assert!(
1207 state
1208 .get_model("app", "users")
1209 .unwrap()
1210 .fields
1211 .contains_key("email")
1212 );
1213 }
1214
1215 #[test]
1216 fn test_add_field_and_unique_together() {
1218 use crate::migrations::ProjectState;
1219 use crate::migrations::operations::*;
1220
1221 let mut state = ProjectState::new();
1222
1223 let create_op = Operation::CreateTable {
1224 name: "users".to_string(),
1225 columns: vec![
1226 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1227 ColumnDefinition::new("email", FieldType::VarChar(255)),
1228 ],
1229 constraints: vec![],
1230 without_rowid: None,
1231 partition: None,
1232 interleave_in_parent: None,
1233 };
1234 create_op.state_forwards("app", &mut state);
1235
1236 let add_op = Operation::AddColumn {
1237 table: "users".to_string(),
1238 column: ColumnDefinition::new("username", FieldType::VarChar(100)),
1239 mysql_options: None,
1240 };
1241 add_op.state_forwards("app", &mut state);
1242
1243 let unique_op = Operation::AlterUniqueTogether {
1244 table: "users".to_string(),
1245 unique_together: vec![vec!["email".to_string(), "username".to_string()]],
1246 };
1247 unique_op.state_forwards("app", &mut state);
1248
1249 assert!(
1250 state
1251 .get_model("app", "users")
1252 .unwrap()
1253 .fields
1254 .contains_key("username")
1255 );
1256 }
1257
1258 #[test]
1259 fn test_add_field_and_unique_together_1() {
1261 use crate::migrations::ProjectState;
1262 use crate::migrations::operations::*;
1263
1264 let mut state = ProjectState::new();
1265
1266 let create_op = Operation::CreateTable {
1267 name: "posts".to_string(),
1268 columns: vec![
1269 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1270 ColumnDefinition::new("title", FieldType::VarChar(255)),
1271 ],
1272 constraints: vec![],
1273 without_rowid: None,
1274 partition: None,
1275 interleave_in_parent: None,
1276 };
1277 create_op.state_forwards("app", &mut state);
1278
1279 let add_op = Operation::AddColumn {
1280 table: "posts".to_string(),
1281 column: ColumnDefinition::new("slug", FieldType::VarChar(255)),
1282 mysql_options: None,
1283 };
1284 add_op.state_forwards("app", &mut state);
1285
1286 let unique_op = Operation::AlterUniqueTogether {
1287 table: "posts".to_string(),
1288 unique_together: vec![vec!["slug".to_string()]],
1289 };
1290 unique_op.state_forwards("app", &mut state);
1291
1292 assert!(
1293 state
1294 .get_model("app", "posts")
1295 .unwrap()
1296 .fields
1297 .contains_key("slug")
1298 );
1299 }
1300
1301 #[test]
1302 fn test_add_field_before_generated_field() {
1304 use crate::migrations::ProjectState;
1305 use crate::migrations::operations::*;
1306
1307 let mut state = ProjectState::new();
1308
1309 let create_op = Operation::CreateTable {
1311 name: "products".to_string(),
1312 columns: vec![
1313 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1314 ColumnDefinition::new(
1315 "price",
1316 FieldType::Decimal {
1317 precision: 10,
1318 scale: 2,
1319 },
1320 ),
1321 ColumnDefinition::new("quantity", FieldType::Integer),
1322 ],
1323 constraints: vec![],
1324 without_rowid: None,
1325 partition: None,
1326 interleave_in_parent: None,
1327 };
1328 create_op.state_forwards("testapp", &mut state);
1329
1330 let add_discount = Operation::AddColumn {
1332 table: "products".to_string(),
1333 column: ColumnDefinition::new(
1334 "discount",
1335 FieldType::Custom("DECIMAL(10,2) DEFAULT 0".to_string()),
1336 ),
1337 mysql_options: None,
1338 };
1339 add_discount.state_forwards("testapp", &mut state);
1340
1341 let add_generated = Operation::AddColumn {
1344 table: "products".to_string(),
1345 column: ColumnDefinition::new(
1346 "total",
1347 FieldType::Custom(
1348 "DECIMAL(10,2) GENERATED ALWAYS AS (price * quantity) STORED".to_string(),
1349 ),
1350 ),
1351 mysql_options: None,
1352 };
1353 add_generated.state_forwards("testapp", &mut state);
1354
1355 let model = state.get_model("testapp", "products").unwrap();
1356 assert!(model.fields.contains_key("discount"));
1357 assert!(model.fields.contains_key("total"));
1358 }
1359
1360 #[test]
1361 fn test_add_field_before_generated_field_1() {
1363 use crate::migrations::ProjectState;
1364 use crate::migrations::operations::*;
1365
1366 let mut state = ProjectState::new();
1367
1368 let create_op = Operation::CreateTable {
1369 name: "users".to_string(),
1370 columns: vec![
1371 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1372 ColumnDefinition::new("first_name", FieldType::VarChar(100)),
1373 ColumnDefinition::new("last_name", FieldType::VarChar(100)),
1374 ],
1375 constraints: vec![],
1376 without_rowid: None,
1377 partition: None,
1378 interleave_in_parent: None,
1379 };
1380 create_op.state_forwards("app", &mut state);
1381
1382 let add_email = Operation::AddColumn {
1384 table: "users".to_string(),
1385 column: ColumnDefinition::new("email", FieldType::VarChar(255)),
1386 mysql_options: None,
1387 };
1388 add_email.state_forwards("app", &mut state);
1389
1390 let add_generated = Operation::AddColumn {
1392 table: "users".to_string(),
1393 column: ColumnDefinition::new(
1394 "full_name",
1395 FieldType::Custom(
1396 "VARCHAR(200) GENERATED ALWAYS AS (first_name || ' ' || last_name) STORED"
1397 .to_string(),
1398 ),
1399 ),
1400 mysql_options: None,
1401 };
1402 add_generated.state_forwards("app", &mut state);
1403
1404 assert!(
1405 state
1406 .get_model("app", "users")
1407 .unwrap()
1408 .fields
1409 .contains_key("full_name")
1410 );
1411 }
1412
1413 #[test]
1414 fn test_add_field_with_default() {
1416 use crate::migrations::ProjectState;
1417 use crate::migrations::operations::*;
1418
1419 let mut state = ProjectState::new();
1420
1421 let create_op = Operation::CreateTable {
1423 name: "users".to_string(),
1424 columns: vec![ColumnDefinition::new(
1425 "id",
1426 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
1427 )],
1428 constraints: vec![],
1429 without_rowid: None,
1430 partition: None,
1431 interleave_in_parent: None,
1432 };
1433 create_op.state_forwards("testapp", &mut state);
1434
1435 let add_op = Operation::AddColumn {
1437 table: "users".to_string(),
1438 column: ColumnDefinition::new(
1439 "status",
1440 FieldType::Custom("VARCHAR(50) DEFAULT 'active'".to_string()),
1441 ),
1442 mysql_options: None,
1443 };
1444 add_op.state_forwards("testapp", &mut state);
1445
1446 let model = state.get_model("testapp", "users").unwrap();
1447 assert!(model.fields.contains_key("status"));
1448 }
1449
1450 #[test]
1451 fn test_add_field_with_default_1() {
1453 use crate::migrations::ProjectState;
1454 use crate::migrations::operations::*;
1455
1456 let mut state = ProjectState::new();
1457
1458 let create_op = Operation::CreateTable {
1459 name: "products".to_string(),
1460 columns: vec![ColumnDefinition::new(
1461 "id",
1462 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
1463 )],
1464 constraints: vec![],
1465 without_rowid: None,
1466 partition: None,
1467 interleave_in_parent: None,
1468 };
1469 create_op.state_forwards("app", &mut state);
1470
1471 let add_op = Operation::AddColumn {
1472 table: "products".to_string(),
1473 column: ColumnDefinition::new(
1474 "price",
1475 FieldType::Custom("DECIMAL(10,2) DEFAULT 0.00".to_string()),
1476 ),
1477 mysql_options: None,
1478 };
1479 add_op.state_forwards("app", &mut state);
1480
1481 assert!(
1482 state
1483 .get_model("app", "products")
1484 .unwrap()
1485 .fields
1486 .contains_key("price")
1487 );
1488 }
1489
1490 #[test]
1491 fn test_add_fk_before_generated_field() {
1493 use crate::migrations::ProjectState;
1494 use crate::migrations::operations::*;
1495
1496 let mut state = ProjectState::new();
1497
1498 let create_categories = Operation::CreateTable {
1500 name: "categories".to_string(),
1501 columns: vec![
1502 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1503 ColumnDefinition::new("name", FieldType::VarChar(100)),
1504 ],
1505 constraints: vec![],
1506 without_rowid: None,
1507 partition: None,
1508 interleave_in_parent: None,
1509 };
1510 create_categories.state_forwards("testapp", &mut state);
1511
1512 let create_products = Operation::CreateTable {
1514 name: "products".to_string(),
1515 columns: vec![
1516 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1517 ColumnDefinition::new("name", FieldType::VarChar(200)),
1518 ColumnDefinition::new(
1519 "price",
1520 FieldType::Decimal {
1521 precision: 10,
1522 scale: 2,
1523 },
1524 ),
1525 ],
1526 constraints: vec![],
1527 without_rowid: None,
1528 partition: None,
1529 interleave_in_parent: None,
1530 };
1531 create_products.state_forwards("testapp", &mut state);
1532
1533 let add_fk = Operation::AddColumn {
1535 table: "products".to_string(),
1536 column: ColumnDefinition::new(
1537 "category_id",
1538 FieldType::Custom("INTEGER REFERENCES categories(id)".to_string()),
1539 ),
1540 mysql_options: None,
1541 };
1542 add_fk.state_forwards("testapp", &mut state);
1543
1544 let add_generated = Operation::AddColumn {
1546 table: "products".to_string(),
1547 column: ColumnDefinition::new(
1548 "display_price",
1549 FieldType::Custom(
1550 "VARCHAR(50) GENERATED ALWAYS AS ('$' || CAST(price AS TEXT)) STORED"
1551 .to_string(),
1552 ),
1553 ),
1554 mysql_options: None,
1555 };
1556 add_generated.state_forwards("testapp", &mut state);
1557
1558 let model = state.get_model("testapp", "products").unwrap();
1559 assert!(model.fields.contains_key("category_id"));
1560 assert!(model.fields.contains_key("display_price"));
1561 }
1562
1563 #[test]
1564 fn test_add_fk_before_generated_field_1() {
1566 use crate::migrations::ProjectState;
1567 use crate::migrations::operations::*;
1568
1569 let mut state = ProjectState::new();
1570
1571 let create_users = Operation::CreateTable {
1572 name: "users".to_string(),
1573 columns: vec![ColumnDefinition::new(
1574 "id",
1575 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
1576 )],
1577 constraints: vec![],
1578 without_rowid: None,
1579 partition: None,
1580 interleave_in_parent: None,
1581 };
1582 create_users.state_forwards("app", &mut state);
1583
1584 let create_orders = Operation::CreateTable {
1585 name: "orders".to_string(),
1586 columns: vec![
1587 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1588 ColumnDefinition::new(
1589 "total",
1590 FieldType::Decimal {
1591 precision: 10,
1592 scale: 2,
1593 },
1594 ),
1595 ],
1596 constraints: vec![],
1597 without_rowid: None,
1598 partition: None,
1599 interleave_in_parent: None,
1600 };
1601 create_orders.state_forwards("app", &mut state);
1602
1603 let add_fk = Operation::AddColumn {
1605 table: "orders".to_string(),
1606 column: ColumnDefinition::new(
1607 "user_id",
1608 FieldType::Custom("INTEGER REFERENCES users(id)".to_string()),
1609 ),
1610 mysql_options: None,
1611 };
1612 add_fk.state_forwards("app", &mut state);
1613
1614 let add_generated = Operation::AddColumn {
1616 table: "orders".to_string(),
1617 column: ColumnDefinition::new(
1618 "total_with_tax",
1619 FieldType::Custom(
1620 "DECIMAL(10,2) GENERATED ALWAYS AS (total * 1.1) STORED".to_string(),
1621 ),
1622 ),
1623 mysql_options: None,
1624 };
1625 add_generated.state_forwards("app", &mut state);
1626
1627 assert!(state.get_model("app", "orders").is_some());
1628 }
1629
1630 #[test]
1631 fn test_add_index_with_new_model() {
1633 use crate::migrations::ProjectState;
1634 use crate::migrations::operations::*;
1635
1636 let mut state = ProjectState::new();
1637
1638 let create_op = Operation::CreateTable {
1640 name: "users".to_string(),
1641 columns: vec![
1642 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1643 ColumnDefinition::new("email", FieldType::VarChar(255)),
1644 ],
1645 constraints: vec![],
1646 without_rowid: None,
1647 partition: None,
1648 interleave_in_parent: None,
1649 };
1650 create_op.state_forwards("testapp", &mut state);
1651
1652 let index_op = Operation::CreateIndex {
1654 table: "users".to_string(),
1655 columns: vec!["email".to_string()],
1656 unique: true,
1657 index_type: None,
1658 where_clause: None,
1659 concurrently: false,
1660 expressions: None,
1661 mysql_options: None,
1662 operator_class: None,
1663 };
1664 let sql = index_op.to_sql(&operations::SqlDialect::Postgres);
1665
1666 assert!(sql.contains("CREATE UNIQUE INDEX"));
1667 assert!(state.get_model("testapp", "users").is_some());
1668 }
1669
1670 #[test]
1671 fn test_add_index_with_new_model_1() {
1673 use crate::migrations::ProjectState;
1674 use crate::migrations::operations::*;
1675
1676 let mut state = ProjectState::new();
1677
1678 let create_op = Operation::CreateTable {
1679 name: "products".to_string(),
1680 columns: vec![
1681 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1682 ColumnDefinition::new("sku", FieldType::VarChar(100)),
1683 ],
1684 constraints: vec![],
1685 without_rowid: None,
1686 partition: None,
1687 interleave_in_parent: None,
1688 };
1689 create_op.state_forwards("app", &mut state);
1690
1691 let index_op = Operation::CreateIndex {
1692 table: "products".to_string(),
1693 columns: vec!["sku".to_string()],
1694 unique: true,
1695 index_type: None,
1696 where_clause: None,
1697 concurrently: false,
1698 expressions: None,
1699 mysql_options: None,
1700 operator_class: None,
1701 };
1702 let sql = index_op.to_sql(&operations::SqlDialect::Sqlite);
1703
1704 assert!(sql.contains("CREATE UNIQUE INDEX"));
1705 assert!(state.get_model("app", "products").is_some());
1706 }
1707
1708 #[test]
1709 fn test_add_indexes() {
1711 use crate::migrations::operations::*;
1712
1713 let op = Operation::CreateIndex {
1715 table: "users".to_string(),
1716 columns: vec!["email".to_string()],
1717 unique: false,
1718 index_type: None,
1719 where_clause: None,
1720 concurrently: false,
1721 expressions: None,
1722 mysql_options: None,
1723 operator_class: None,
1724 };
1725
1726 let sql = op.to_sql(&SqlDialect::Postgres);
1727 assert!(sql.contains("CREATE INDEX"));
1728 assert!(sql.contains("users"));
1729 assert!(sql.contains("email"));
1730 }
1731
1732 #[test]
1733 fn test_add_indexes_1() {
1735 use crate::migrations::operations::*;
1736
1737 let op = Operation::CreateIndex {
1739 table: "products".to_string(),
1740 columns: vec!["sku".to_string()],
1741 unique: true,
1742 index_type: None,
1743 where_clause: None,
1744 concurrently: false,
1745 expressions: None,
1746 mysql_options: None,
1747 operator_class: None,
1748 };
1749
1750 let sql = op.to_sql(&SqlDialect::Postgres);
1751 assert!(sql.contains("CREATE UNIQUE INDEX"));
1752 assert!(sql.contains("products"));
1753 assert!(sql.contains("sku"));
1754 }
1755
1756 #[test]
1757 fn test_add_many_to_many() {
1759 use crate::migrations::ProjectState;
1760 use crate::migrations::operations::*;
1761
1762 let mut state = ProjectState::new();
1763
1764 let create_students = Operation::CreateTable {
1766 name: "students".to_string(),
1767 columns: vec![
1768 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1769 ColumnDefinition::new("name", FieldType::VarChar(100)),
1770 ],
1771 constraints: vec![],
1772 without_rowid: None,
1773 partition: None,
1774 interleave_in_parent: None,
1775 };
1776 create_students.state_forwards("testapp", &mut state);
1777
1778 let create_courses = Operation::CreateTable {
1780 name: "courses".to_string(),
1781 columns: vec![
1782 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1783 ColumnDefinition::new("title", FieldType::VarChar(200)),
1784 ],
1785 constraints: vec![],
1786 without_rowid: None,
1787 partition: None,
1788 interleave_in_parent: None,
1789 };
1790 create_courses.state_forwards("testapp", &mut state);
1791
1792 let create_m2m = Operation::CreateTable {
1795 name: "students_courses".to_string(),
1796 columns: vec![
1797 ColumnDefinition::new(
1798 "student_id",
1799 FieldType::Custom("INTEGER REFERENCES students(id)".to_string()),
1800 ),
1801 ColumnDefinition::new(
1802 "course_id",
1803 FieldType::Custom("INTEGER REFERENCES courses(id)".to_string()),
1804 ),
1805 ],
1806 constraints: vec![],
1807 without_rowid: None,
1808 partition: None,
1809 interleave_in_parent: None,
1810 };
1811 create_m2m.state_forwards("testapp", &mut state);
1812
1813 assert!(state.get_model("testapp", "students_courses").is_some());
1814 }
1815
1816 #[test]
1817 fn test_add_many_to_many_1() {
1819 use crate::migrations::ProjectState;
1820 use crate::migrations::operations::*;
1821
1822 let mut state = ProjectState::new();
1823
1824 let create_tags = Operation::CreateTable {
1825 name: "tags".to_string(),
1826 columns: vec![
1827 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1828 ColumnDefinition::new("name", FieldType::VarChar(50)),
1829 ],
1830 constraints: vec![],
1831 without_rowid: None,
1832 partition: None,
1833 interleave_in_parent: None,
1834 };
1835 create_tags.state_forwards("app", &mut state);
1836
1837 let create_posts = Operation::CreateTable {
1838 name: "posts".to_string(),
1839 columns: vec![
1840 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1841 ColumnDefinition::new("title", FieldType::VarChar(255)),
1842 ],
1843 constraints: vec![],
1844 without_rowid: None,
1845 partition: None,
1846 interleave_in_parent: None,
1847 };
1848 create_posts.state_forwards("app", &mut state);
1849
1850 let create_assoc = Operation::CreateTable {
1853 name: "posts_tags".to_string(),
1854 columns: vec![
1855 ColumnDefinition::new(
1856 "post_id",
1857 FieldType::Custom("INTEGER REFERENCES posts(id)".to_string()),
1858 ),
1859 ColumnDefinition::new(
1860 "tag_id",
1861 FieldType::Custom("INTEGER REFERENCES tags(id)".to_string()),
1862 ),
1863 ],
1864 constraints: vec![],
1865 without_rowid: None,
1866 partition: None,
1867 interleave_in_parent: None,
1868 };
1869 create_assoc.state_forwards("app", &mut state);
1870
1871 assert!(state.get_model("app", "posts_tags").is_some());
1872 }
1873
1874 #[test]
1875 fn test_add_model_order_with_respect_to() {
1877 use crate::migrations::ProjectState;
1878 use crate::migrations::operations::*;
1879
1880 let mut state = ProjectState::new();
1881
1882 let create_parent = Operation::CreateTable {
1884 name: "parent".to_string(),
1885 columns: vec![ColumnDefinition::new(
1886 "id",
1887 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
1888 )],
1889 constraints: vec![],
1890 without_rowid: None,
1891 partition: None,
1892 interleave_in_parent: None,
1893 };
1894 create_parent.state_forwards("app", &mut state);
1895
1896 let create_child = Operation::CreateTable {
1897 name: "child".to_string(),
1898 columns: vec![
1899 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1900 ColumnDefinition::new(
1901 "parent_id",
1902 FieldType::Custom("INTEGER REFERENCES parent(id)".to_string()),
1903 ),
1904 ColumnDefinition::new(
1905 "_order",
1906 FieldType::Custom("INTEGER NOT NULL DEFAULT 0".to_string()),
1907 ),
1908 ],
1909 constraints: vec![],
1910 without_rowid: None,
1911 partition: None,
1912 interleave_in_parent: None,
1913 };
1914 create_child.state_forwards("app", &mut state);
1915
1916 assert!(
1917 state
1918 .get_model("app", "child")
1919 .unwrap()
1920 .fields
1921 .contains_key("_order")
1922 );
1923 }
1924
1925 #[test]
1926 fn test_add_model_order_with_respect_to_1() {
1928 use crate::migrations::ProjectState;
1929 use crate::migrations::operations::*;
1930
1931 let mut state = ProjectState::new();
1932
1933 let create_op = Operation::CreateTable {
1934 name: "ordered_items".to_string(),
1935 columns: vec![
1936 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1937 ColumnDefinition::new("container_id", FieldType::Integer),
1938 ColumnDefinition::new(
1939 "_order",
1940 FieldType::Custom("INTEGER NOT NULL DEFAULT 0".to_string()),
1941 ),
1942 ],
1943 constraints: vec![],
1944 without_rowid: None,
1945 partition: None,
1946 interleave_in_parent: None,
1947 };
1948 create_op.state_forwards("app", &mut state);
1949
1950 assert!(state.get_model("app", "ordered_items").is_some());
1951 }
1952
1953 #[test]
1954 fn test_add_model_order_with_respect_to_constraint() {
1956 use crate::migrations::ProjectState;
1957 use crate::migrations::operations::*;
1958
1959 let mut state = ProjectState::new();
1960
1961 let create_op = Operation::CreateTable {
1962 name: "items".to_string(),
1963 columns: vec![
1964 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1965 ColumnDefinition::new("parent_id", FieldType::Integer),
1966 ColumnDefinition::new(
1967 "_order",
1968 FieldType::Custom("INTEGER NOT NULL DEFAULT 0".to_string()),
1969 ),
1970 ],
1971 constraints: vec![Constraint::Check {
1972 name: "order_non_negative".to_string(),
1973 expression: "_order >= 0".to_string(),
1974 }],
1975 without_rowid: None,
1976 partition: None,
1977 interleave_in_parent: None,
1978 };
1979 create_op.state_forwards("app", &mut state);
1980
1981 assert!(state.get_model("app", "items").is_some());
1982 }
1983
1984 #[test]
1985 fn test_add_model_order_with_respect_to_constraint_1() {
1987 use crate::migrations::ProjectState;
1988 use crate::migrations::operations::*;
1989
1990 let mut state = ProjectState::new();
1991
1992 let create_op = Operation::CreateTable {
1993 name: "entries".to_string(),
1994 columns: vec![
1995 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
1996 ColumnDefinition::new("group_id", FieldType::Integer),
1997 ColumnDefinition::new("_order", FieldType::Custom("INTEGER NOT NULL".to_string())),
1998 ],
1999 constraints: vec![Constraint::Check {
2000 name: "order_non_negative".to_string(),
2001 expression: "_order >= 0".to_string(),
2002 }],
2003 without_rowid: None,
2004 partition: None,
2005 interleave_in_parent: None,
2006 };
2007 create_op.state_forwards("app", &mut state);
2008
2009 assert!(state.get_model("app", "entries").is_some());
2010 }
2011
2012 #[test]
2013 fn test_add_model_order_with_respect_to_index() {
2015 use crate::migrations::ProjectState;
2016 use crate::migrations::operations::*;
2017
2018 let mut state = ProjectState::new();
2019
2020 let create_op = Operation::CreateTable {
2021 name: "items".to_string(),
2022 columns: vec![
2023 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
2024 ColumnDefinition::new("parent_id", FieldType::Integer),
2025 ColumnDefinition::new(
2026 "_order",
2027 FieldType::Custom("INTEGER NOT NULL DEFAULT 0".to_string()),
2028 ),
2029 ],
2030 constraints: vec![],
2031 without_rowid: None,
2032 partition: None,
2033 interleave_in_parent: None,
2034 };
2035 create_op.state_forwards("app", &mut state);
2036
2037 let _create_index = Operation::CreateIndex {
2039 table: "items".to_string(),
2040 columns: vec!["parent_id".to_string(), "_order".to_string()],
2041 unique: false,
2042 index_type: None,
2043 where_clause: None,
2044 concurrently: false,
2045 expressions: None,
2046 mysql_options: None,
2047 operator_class: None,
2048 };
2049
2050 assert!(state.get_model("app", "items").is_some());
2051 }
2052
2053 #[test]
2054 fn test_add_model_order_with_respect_to_index_1() {
2056 use crate::migrations::ProjectState;
2057 use crate::migrations::operations::*;
2058
2059 let mut state = ProjectState::new();
2060
2061 let create_op = Operation::CreateTable {
2062 name: "tasks".to_string(),
2063 columns: vec![
2064 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
2065 ColumnDefinition::new("project_id", FieldType::Integer),
2066 ColumnDefinition::new("_order", FieldType::Custom("INTEGER NOT NULL".to_string())),
2067 ],
2068 constraints: vec![],
2069 without_rowid: None,
2070 partition: None,
2071 interleave_in_parent: None,
2072 };
2073 create_op.state_forwards("app", &mut state);
2074
2075 assert!(state.get_model("app", "tasks").is_some());
2076 }
2077
2078 #[test]
2079 fn test_add_model_order_with_respect_to_unique_together() {
2081 use crate::migrations::ProjectState;
2082 use crate::migrations::operations::*;
2083
2084 let mut state = ProjectState::new();
2085
2086 let create_op = Operation::CreateTable {
2087 name: "items".to_string(),
2088 columns: vec![
2089 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
2090 ColumnDefinition::new("parent_id", FieldType::Integer),
2091 ColumnDefinition::new("_order", FieldType::Custom("INTEGER NOT NULL".to_string())),
2092 ],
2093 constraints: vec![],
2094 without_rowid: None,
2095 partition: None,
2096 interleave_in_parent: None,
2097 };
2098 create_op.state_forwards("app", &mut state);
2099
2100 let unique_op = Operation::AlterUniqueTogether {
2101 table: "items".to_string(),
2102 unique_together: vec![vec!["parent_id".to_string(), "_order".to_string()]],
2103 };
2104 unique_op.state_forwards("app", &mut state);
2105
2106 assert!(state.get_model("app", "items").is_some());
2107 }
2108
2109 #[test]
2110 fn test_add_model_order_with_respect_to_unique_together_1() {
2112 use crate::migrations::ProjectState;
2113 use crate::migrations::operations::*;
2114
2115 let mut state = ProjectState::new();
2116
2117 let create_op = Operation::CreateTable {
2118 name: "slides".to_string(),
2119 columns: vec![
2120 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
2121 ColumnDefinition::new("deck_id", FieldType::Integer),
2122 ColumnDefinition::new("_order", FieldType::Custom("INTEGER NOT NULL".to_string())),
2123 ],
2124 constraints: vec![],
2125 without_rowid: None,
2126 partition: None,
2127 interleave_in_parent: None,
2128 };
2129 create_op.state_forwards("app", &mut state);
2130
2131 let unique_op = Operation::AlterUniqueTogether {
2132 table: "slides".to_string(),
2133 unique_together: vec![vec!["deck_id".to_string(), "_order".to_string()]],
2134 };
2135 unique_op.state_forwards("app", &mut state);
2136
2137 assert!(state.get_model("app", "slides").is_some());
2138 }
2139
2140 #[test]
2141 fn test_add_model_with_field_removed_from_base_model() {
2143 use crate::migrations::ProjectState;
2146 use crate::migrations::operations::*;
2147
2148 let mut state = ProjectState::new();
2149
2150 let create_base = Operation::CreateTable {
2152 name: "employees".to_string(),
2153 columns: vec![
2154 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
2155 ColumnDefinition::new("name", FieldType::VarChar(100)),
2156 ColumnDefinition::new("email", FieldType::VarChar(100)),
2157 ],
2158 constraints: vec![],
2159 without_rowid: None,
2160 partition: None,
2161 interleave_in_parent: None,
2162 };
2163 create_base.state_forwards("company", &mut state);
2164
2165 let create_inherited = Operation::CreateInheritedTable {
2167 name: "managers".to_string(),
2168 columns: vec![
2169 ColumnDefinition::new("department", FieldType::VarChar(100)),
2170 ColumnDefinition::new(
2171 "budget",
2172 FieldType::Decimal {
2173 precision: 10,
2174 scale: 2,
2175 },
2176 ),
2177 ],
2178 base_table: "employees".to_string(),
2179 join_column: "employee_id".to_string(),
2180 };
2181 create_inherited.state_forwards("company", &mut state);
2182
2183 let manager_model = state.get_model("company", "managers").unwrap();
2184 assert!(manager_model.fields.contains_key("employee_id"));
2185 assert!(manager_model.fields.contains_key("department"));
2186 assert!(manager_model.fields.contains_key("budget"));
2187 assert_eq!(manager_model.base_model, Some("employees".to_string()));
2188 assert_eq!(
2189 manager_model.inheritance_type,
2190 Some("joined_table".to_string())
2191 );
2192 }
2193
2194 #[test]
2195 fn test_add_model_with_field_removed_from_base_model_1() {
2197 use crate::migrations::ProjectState;
2200 use crate::migrations::operations::*;
2201
2202 let mut state = ProjectState::new();
2203
2204 let create_base = Operation::CreateTable {
2206 name: "persons".to_string(),
2207 columns: vec![
2208 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
2209 ColumnDefinition::new("name", FieldType::VarChar(100)),
2210 ColumnDefinition::new("email", FieldType::VarChar(100)),
2211 ColumnDefinition::new("student_id", FieldType::VarChar(20)),
2213 ColumnDefinition::new("grade", FieldType::VarChar(10)),
2214 ColumnDefinition::new("employee_id", FieldType::VarChar(20)),
2215 ColumnDefinition::new("department", FieldType::VarChar(100)),
2216 ],
2217 constraints: vec![],
2218 without_rowid: None,
2219 partition: None,
2220 interleave_in_parent: None,
2221 };
2222 create_base.state_forwards("school", &mut state);
2223
2224 let add_discriminator = Operation::AddDiscriminatorColumn {
2226 table: "persons".to_string(),
2227 column_name: "person_type".to_string(),
2228 default_value: "person".to_string(),
2229 };
2230 add_discriminator.state_forwards("school", &mut state);
2231
2232 let person_model = state.get_model("school", "persons").unwrap();
2233 assert!(person_model.fields.contains_key("person_type"));
2234 assert_eq!(
2235 person_model.discriminator_column,
2236 Some("person_type".to_string())
2237 );
2238 assert_eq!(
2239 person_model.inheritance_type,
2240 Some("single_table".to_string())
2241 );
2242 }
2243
2244 #[test]
2245 fn test_add_non_blank_textfield_and_charfield() {
2247 use crate::migrations::ProjectState;
2248 use crate::migrations::operations::*;
2249
2250 let mut state = ProjectState::new();
2251
2252 let create_op = Operation::CreateTable {
2253 name: "articles".to_string(),
2254 columns: vec![ColumnDefinition::new(
2255 "id",
2256 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
2257 )],
2258 constraints: vec![],
2259 without_rowid: None,
2260 partition: None,
2261 interleave_in_parent: None,
2262 };
2263 create_op.state_forwards("testapp", &mut state);
2264
2265 let add_text = Operation::AddColumn {
2267 table: "articles".to_string(),
2268 column: ColumnDefinition::new(
2269 "content",
2270 FieldType::Custom("TEXT NOT NULL DEFAULT ''".to_string()),
2271 ),
2272 mysql_options: None,
2273 };
2274 add_text.state_forwards("testapp", &mut state);
2275
2276 let add_char = Operation::AddColumn {
2277 table: "articles".to_string(),
2278 column: ColumnDefinition::new(
2279 "title",
2280 FieldType::Custom("VARCHAR(255) NOT NULL DEFAULT ''".to_string()),
2281 ),
2282 mysql_options: None,
2283 };
2284 add_char.state_forwards("testapp", &mut state);
2285
2286 let model = state.get_model("testapp", "articles").unwrap();
2287 assert!(model.fields.contains_key("content"));
2288 assert!(model.fields.contains_key("title"));
2289 }
2290
2291 #[test]
2292 fn test_add_non_blank_textfield_and_charfield_1() {
2294 use crate::migrations::ProjectState;
2295 use crate::migrations::operations::*;
2296
2297 let mut state = ProjectState::new();
2298
2299 let create_op = Operation::CreateTable {
2300 name: "posts".to_string(),
2301 columns: vec![ColumnDefinition::new(
2302 "id",
2303 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
2304 )],
2305 constraints: vec![],
2306 without_rowid: None,
2307 partition: None,
2308 interleave_in_parent: None,
2309 };
2310 create_op.state_forwards("app", &mut state);
2311
2312 let add_op = Operation::AddColumn {
2313 table: "posts".to_string(),
2314 column: ColumnDefinition::new("body", FieldType::Custom("TEXT NOT NULL".to_string())),
2315 mysql_options: None,
2316 };
2317 add_op.state_forwards("app", &mut state);
2318
2319 assert!(
2320 state
2321 .get_model("app", "posts")
2322 .unwrap()
2323 .fields
2324 .contains_key("body")
2325 );
2326 }
2327
2328 #[test]
2329 fn test_add_not_null_field_with_db_default() {
2331 use crate::migrations::ProjectState;
2332 use crate::migrations::operations::*;
2333
2334 let mut state = ProjectState::new();
2335
2336 let create_op = Operation::CreateTable {
2337 name: "users".to_string(),
2338 columns: vec![ColumnDefinition::new(
2339 "id",
2340 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
2341 )],
2342 constraints: vec![],
2343 without_rowid: None,
2344 partition: None,
2345 interleave_in_parent: None,
2346 };
2347 create_op.state_forwards("testapp", &mut state);
2348
2349 let add_op = Operation::AddColumn {
2351 table: "users".to_string(),
2352 column: ColumnDefinition::new(
2353 "created_at",
2354 FieldType::Custom("TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP".to_string()),
2355 ),
2356 mysql_options: None,
2357 };
2358 add_op.state_forwards("testapp", &mut state);
2359
2360 let model = state.get_model("testapp", "users").unwrap();
2361 assert!(model.fields.contains_key("created_at"));
2362 }
2363
2364 #[test]
2365 fn test_add_not_null_field_with_db_default_1() {
2367 use crate::migrations::ProjectState;
2368 use crate::migrations::operations::*;
2369
2370 let mut state = ProjectState::new();
2371
2372 let create_op = Operation::CreateTable {
2373 name: "orders".to_string(),
2374 columns: vec![ColumnDefinition::new(
2375 "id",
2376 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
2377 )],
2378 constraints: vec![],
2379 without_rowid: None,
2380 partition: None,
2381 interleave_in_parent: None,
2382 };
2383 create_op.state_forwards("app", &mut state);
2384
2385 let add_op = Operation::AddColumn {
2386 table: "orders".to_string(),
2387 column: ColumnDefinition::new(
2388 "status",
2389 FieldType::Custom("VARCHAR(50) NOT NULL DEFAULT 'pending'".to_string()),
2390 ),
2391 mysql_options: None,
2392 };
2393 add_op.state_forwards("app", &mut state);
2394
2395 assert!(
2396 state
2397 .get_model("app", "orders")
2398 .unwrap()
2399 .fields
2400 .contains_key("status")
2401 );
2402 }
2403
2404 #[test]
2405 fn test_add_unique_together() {
2407 use crate::migrations::ProjectState;
2408 use crate::migrations::operations::*;
2409
2410 let mut state = ProjectState::new();
2411
2412 let create_op = Operation::CreateTable {
2413 name: "products".to_string(),
2414 columns: vec![
2415 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
2416 ColumnDefinition::new("name", FieldType::VarChar(255)),
2417 ColumnDefinition::new("sku", FieldType::VarChar(50)),
2418 ],
2419 constraints: vec![],
2420 without_rowid: None,
2421 partition: None,
2422 interleave_in_parent: None,
2423 };
2424 create_op.state_forwards("app", &mut state);
2425
2426 let unique_op = Operation::AlterUniqueTogether {
2427 table: "products".to_string(),
2428 unique_together: vec![vec!["name".to_string(), "sku".to_string()]],
2429 };
2430 unique_op.state_forwards("app", &mut state);
2431
2432 assert!(state.get_model("app", "products").is_some());
2433 }
2434
2435 #[test]
2436 fn test_add_unique_together_1() {
2438 use crate::migrations::ProjectState;
2439 use crate::migrations::operations::*;
2440
2441 let mut state = ProjectState::new();
2442
2443 let create_op = Operation::CreateTable {
2444 name: "books".to_string(),
2445 columns: vec![
2446 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
2447 ColumnDefinition::new("title", FieldType::VarChar(255)),
2448 ColumnDefinition::new("author", FieldType::VarChar(255)),
2449 ColumnDefinition::new("isbn", FieldType::VarChar(20)),
2450 ],
2451 constraints: vec![],
2452 without_rowid: None,
2453 partition: None,
2454 interleave_in_parent: None,
2455 };
2456 create_op.state_forwards("app", &mut state);
2457
2458 let unique_op = Operation::AlterUniqueTogether {
2459 table: "books".to_string(),
2460 unique_together: vec![
2461 vec!["title".to_string(), "author".to_string()],
2462 vec!["isbn".to_string()],
2463 ],
2464 };
2465 unique_op.state_forwards("app", &mut state);
2466
2467 assert!(state.get_model("app", "books").is_some());
2468 }
2469
2470 #[test]
2471 fn test_alter_constraint() {
2473 use crate::migrations::operations::*;
2474
2475 let drop_op = Operation::DropConstraint {
2477 table: "users".to_string(),
2478 constraint_name: "old_constraint".to_string(),
2479 };
2480
2481 let add_op = Operation::AddConstraint {
2482 table: "users".to_string(),
2483 constraint_sql: "CHECK (age >= 21)".to_string(),
2484 };
2485
2486 let drop_sql = drop_op.to_sql(&SqlDialect::Postgres);
2487 let add_sql = add_op.to_sql(&SqlDialect::Postgres);
2488
2489 assert!(drop_sql.contains("DROP CONSTRAINT"));
2490 assert!(add_sql.contains("ADD CHECK (age >= 21)"));
2491 }
2492
2493 #[test]
2494 fn test_alter_constraint_1() {
2496 use crate::migrations::operations::*;
2497
2498 let drop_op = Operation::DropConstraint {
2500 table: "products".to_string(),
2501 constraint_name: "price_check".to_string(),
2502 };
2503
2504 let add_op = Operation::AddConstraint {
2505 table: "products".to_string(),
2506 constraint_sql: "CHECK (price > 0)".to_string(),
2507 };
2508
2509 let drop_sql = drop_op.to_sql(&SqlDialect::Postgres);
2510 let add_sql = add_op.to_sql(&SqlDialect::Postgres);
2511
2512 assert!(drop_sql.contains("DROP CONSTRAINT price_check"));
2513 assert!(add_sql.contains("ADD CHECK (price > 0)"));
2514 }
2515
2516 #[test]
2517 fn test_alter_db_table_add() {
2519 use crate::migrations::ProjectState;
2520 use crate::migrations::operations::*;
2521
2522 let mut state = ProjectState::new();
2523
2524 let create_op = Operation::CreateTable {
2526 name: "myapp_user".to_string(),
2527 columns: vec![ColumnDefinition::new(
2528 "id",
2529 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
2530 )],
2531 constraints: vec![],
2532 without_rowid: None,
2533 partition: None,
2534 interleave_in_parent: None,
2535 };
2536 create_op.state_forwards("testapp", &mut state);
2537
2538 let rename_op = Operation::RenameTable {
2540 old_name: "myapp_user".to_string(),
2541 new_name: "custom_users".to_string(),
2542 };
2543 rename_op.state_forwards("testapp", &mut state);
2544
2545 assert!(state.get_model("testapp", "custom_users").is_some());
2546 }
2547
2548 #[test]
2549 fn test_alter_db_table_add_1() {
2551 use crate::migrations::ProjectState;
2552 use crate::migrations::operations::*;
2553
2554 let mut state = ProjectState::new();
2555
2556 let create_op = Operation::CreateTable {
2557 name: "app_product".to_string(),
2558 columns: vec![ColumnDefinition::new(
2559 "id",
2560 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
2561 )],
2562 constraints: vec![],
2563 without_rowid: None,
2564 partition: None,
2565 interleave_in_parent: None,
2566 };
2567 create_op.state_forwards("app", &mut state);
2568
2569 let rename_op = Operation::RenameTable {
2570 old_name: "app_product".to_string(),
2571 new_name: "products_table".to_string(),
2572 };
2573 rename_op.state_forwards("app", &mut state);
2574
2575 assert!(state.get_model("app", "products_table").is_some());
2576 }
2577
2578 #[test]
2579 fn test_alter_db_table_change() {
2581 use crate::migrations::ProjectState;
2582 use crate::migrations::operations::*;
2583
2584 let mut state = ProjectState::new();
2585
2586 let create_op = Operation::CreateTable {
2588 name: "old_table".to_string(),
2589 columns: vec![ColumnDefinition::new(
2590 "id",
2591 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
2592 )],
2593 constraints: vec![],
2594 without_rowid: None,
2595 partition: None,
2596 interleave_in_parent: None,
2597 };
2598 create_op.state_forwards("testapp", &mut state);
2599
2600 let rename_op = Operation::RenameTable {
2602 old_name: "old_table".to_string(),
2603 new_name: "new_table".to_string(),
2604 };
2605 rename_op.state_forwards("testapp", &mut state);
2606
2607 assert!(state.get_model("testapp", "old_table").is_none());
2608 assert!(state.get_model("testapp", "new_table").is_some());
2609 }
2610
2611 #[test]
2612 fn test_alter_db_table_change_1() {
2614 use crate::migrations::ProjectState;
2615 use crate::migrations::operations::*;
2616
2617 let mut state = ProjectState::new();
2618
2619 let create_op = Operation::CreateTable {
2620 name: "users".to_string(),
2621 columns: vec![ColumnDefinition::new(
2622 "id",
2623 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
2624 )],
2625 constraints: vec![],
2626 without_rowid: None,
2627 partition: None,
2628 interleave_in_parent: None,
2629 };
2630 create_op.state_forwards("app", &mut state);
2631
2632 let rename_op = Operation::RenameTable {
2633 old_name: "users".to_string(),
2634 new_name: "customers".to_string(),
2635 };
2636 rename_op.state_forwards("app", &mut state);
2637
2638 assert!(state.get_model("app", "customers").is_some());
2639 }
2640
2641 #[test]
2642 fn test_alter_db_table_comment_add() {
2644 use crate::migrations::operations::*;
2645
2646 let op = Operation::AlterTableComment {
2647 table: "users".to_string(),
2648 comment: Some("User accounts table".to_string()),
2649 };
2650
2651 let sql = op.to_sql(&SqlDialect::Postgres);
2652 assert!(sql.contains("COMMENT ON TABLE users"));
2653 assert!(sql.contains("User accounts table"));
2654 }
2655
2656 #[test]
2657 fn test_alter_db_table_comment_add_1() {
2659 use crate::migrations::operations::*;
2660
2661 let op = Operation::AlterTableComment {
2662 table: "products".to_string(),
2663 comment: Some("Product catalog".to_string()),
2664 };
2665
2666 let sql = op.to_sql(&SqlDialect::Mysql);
2667 assert!(sql.contains("ALTER TABLE products"));
2668 assert!(sql.contains("COMMENT='Product catalog'"));
2669 }
2670
2671 #[test]
2672 fn test_alter_db_table_comment_change() {
2674 use crate::migrations::operations::*;
2675
2676 let op = Operation::AlterTableComment {
2677 table: "users".to_string(),
2678 comment: Some("Updated user table".to_string()),
2679 };
2680
2681 let sql = op.to_sql(&SqlDialect::Postgres);
2682 assert!(sql.contains("COMMENT ON TABLE users"));
2683 assert!(sql.contains("Updated user table"));
2684 }
2685
2686 #[test]
2687 fn test_alter_db_table_comment_change_1() {
2689 use crate::migrations::operations::*;
2690
2691 let op = Operation::AlterTableComment {
2692 table: "orders".to_string(),
2693 comment: Some("Order history".to_string()),
2694 };
2695
2696 let sql = op.to_sql(&SqlDialect::Mysql);
2697 assert!(sql.contains("ALTER TABLE orders"));
2698 }
2699
2700 #[test]
2701 fn test_alter_db_table_comment_no_changes() {
2703 use crate::migrations::operations::*;
2704
2705 let op = Operation::AlterTableComment {
2707 table: "users".to_string(),
2708 comment: Some("Same comment".to_string()),
2709 };
2710
2711 let sql = op.to_sql(&SqlDialect::Postgres);
2712 assert!(sql.contains("COMMENT ON TABLE users"));
2713 }
2714
2715 #[test]
2716 fn test_alter_db_table_comment_no_changes_1() {
2718 use crate::migrations::operations::*;
2719
2720 let op = Operation::AlterTableComment {
2721 table: "products".to_string(),
2722 comment: Some("No change".to_string()),
2723 };
2724
2725 let sql = op.to_sql(&SqlDialect::Mysql);
2726 assert!(!sql.is_empty());
2727 }
2728
2729 #[test]
2730 fn test_alter_db_table_comment_remove() {
2732 use crate::migrations::operations::*;
2733
2734 let op = Operation::AlterTableComment {
2735 table: "users".to_string(),
2736 comment: None,
2737 };
2738
2739 let sql = op.to_sql(&SqlDialect::Postgres);
2740 assert!(sql.contains("COMMENT ON TABLE users IS NULL"));
2741 }
2742
2743 #[test]
2744 fn test_alter_db_table_comment_remove_1() {
2746 use crate::migrations::operations::*;
2747
2748 let op = Operation::AlterTableComment {
2749 table: "orders".to_string(),
2750 comment: None,
2751 };
2752
2753 let sql = op.to_sql(&SqlDialect::Mysql);
2754 assert!(sql.contains("COMMENT=''"));
2755 }
2756
2757 #[test]
2758 fn test_alter_db_table_no_changes() {
2760 use crate::migrations::ProjectState;
2761 use crate::migrations::operations::*;
2762
2763 let mut state = ProjectState::new();
2764
2765 let create_op = Operation::CreateTable {
2767 name: "users".to_string(),
2768 columns: vec![ColumnDefinition::new(
2769 "id",
2770 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
2771 )],
2772 constraints: vec![],
2773 without_rowid: None,
2774 partition: None,
2775 interleave_in_parent: None,
2776 };
2777 create_op.state_forwards("testapp", &mut state);
2778
2779 let rename_op = Operation::RenameTable {
2781 old_name: "users".to_string(),
2782 new_name: "users".to_string(),
2783 };
2784 rename_op.state_forwards("testapp", &mut state);
2785
2786 assert!(state.get_model("testapp", "users").is_some());
2788 }
2789
2790 #[test]
2791 fn test_alter_db_table_no_changes_1() {
2793 use crate::migrations::ProjectState;
2794 use crate::migrations::operations::*;
2795
2796 let mut state = ProjectState::new();
2797
2798 let create_op = Operation::CreateTable {
2799 name: "products".to_string(),
2800 columns: vec![ColumnDefinition::new(
2801 "id",
2802 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
2803 )],
2804 constraints: vec![],
2805 without_rowid: None,
2806 partition: None,
2807 interleave_in_parent: None,
2808 };
2809 create_op.state_forwards("app", &mut state);
2810
2811 let rename_op = Operation::RenameTable {
2813 old_name: "products".to_string(),
2814 new_name: "products".to_string(),
2815 };
2816 rename_op.state_forwards("app", &mut state);
2817
2818 assert!(state.get_model("app", "products").is_some());
2819 }
2820
2821 #[test]
2822 fn test_alter_db_table_remove() {
2824 use crate::migrations::ProjectState;
2825 use crate::migrations::operations::*;
2826
2827 let mut state = ProjectState::new();
2828
2829 let create_op = Operation::CreateTable {
2830 name: "custom_table".to_string(),
2831 columns: vec![ColumnDefinition::new(
2832 "id",
2833 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
2834 )],
2835 constraints: vec![],
2836 without_rowid: None,
2837 partition: None,
2838 interleave_in_parent: None,
2839 };
2840 create_op.state_forwards("testapp", &mut state);
2841
2842 let rename_op = Operation::RenameTable {
2844 old_name: "custom_table".to_string(),
2845 new_name: "myapp_model".to_string(),
2846 };
2847 rename_op.state_forwards("testapp", &mut state);
2848
2849 assert!(state.get_model("testapp", "myapp_model").is_some());
2850 }
2851
2852 #[test]
2853 fn test_alter_db_table_remove_1() {
2855 use crate::migrations::ProjectState;
2856 use crate::migrations::operations::*;
2857
2858 let mut state = ProjectState::new();
2859
2860 let create_op = Operation::CreateTable {
2861 name: "old_custom".to_string(),
2862 columns: vec![ColumnDefinition::new(
2863 "id",
2864 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
2865 )],
2866 constraints: vec![],
2867 without_rowid: None,
2868 partition: None,
2869 interleave_in_parent: None,
2870 };
2871 create_op.state_forwards("app", &mut state);
2872
2873 let rename_op = Operation::RenameTable {
2874 old_name: "old_custom".to_string(),
2875 new_name: "app_default".to_string(),
2876 };
2877 rename_op.state_forwards("app", &mut state);
2878
2879 assert!(state.get_model("app", "app_default").is_some());
2880 }
2881
2882 #[test]
2883 fn test_alter_db_table_with_model_change() {
2885 use crate::migrations::ProjectState;
2886 use crate::migrations::operations::*;
2887
2888 let mut state = ProjectState::new();
2889
2890 let create_op = Operation::CreateTable {
2891 name: "users".to_string(),
2892 columns: vec![
2893 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
2894 ColumnDefinition::new("name", FieldType::VarChar(100)),
2895 ],
2896 constraints: vec![],
2897 without_rowid: None,
2898 partition: None,
2899 interleave_in_parent: None,
2900 };
2901 create_op.state_forwards("testapp", &mut state);
2902
2903 let rename_op = Operation::RenameTable {
2905 old_name: "users".to_string(),
2906 new_name: "custom_users".to_string(),
2907 };
2908 rename_op.state_forwards("testapp", &mut state);
2909
2910 let add_field = Operation::AddColumn {
2911 table: "custom_users".to_string(),
2912 column: ColumnDefinition::new("email", FieldType::VarChar(255)),
2913 mysql_options: None,
2914 };
2915 add_field.state_forwards("testapp", &mut state);
2916
2917 let model = state.get_model("testapp", "custom_users").unwrap();
2918 assert!(model.fields.contains_key("email"));
2919 }
2920
2921 #[test]
2922 fn test_alter_db_table_with_model_change_1() {
2924 use crate::migrations::ProjectState;
2925 use crate::migrations::operations::*;
2926
2927 let mut state = ProjectState::new();
2928
2929 let create_op = Operation::CreateTable {
2930 name: "items".to_string(),
2931 columns: vec![ColumnDefinition::new(
2932 "id",
2933 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
2934 )],
2935 constraints: vec![],
2936 without_rowid: None,
2937 partition: None,
2938 interleave_in_parent: None,
2939 };
2940 create_op.state_forwards("app", &mut state);
2941
2942 let rename_op = Operation::RenameTable {
2943 old_name: "items".to_string(),
2944 new_name: "products".to_string(),
2945 };
2946 rename_op.state_forwards("app", &mut state);
2947
2948 assert!(state.get_model("app", "products").is_some());
2949 }
2950
2951 #[test]
2952 fn test_alter_field() {
2954 use crate::migrations::ProjectState;
2955 use crate::migrations::operations::*;
2956
2957 let mut state = ProjectState::new();
2958
2959 let create_op = Operation::CreateTable {
2961 name: "test_table".to_string(),
2962 columns: vec![
2963 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
2964 ColumnDefinition::new("name", FieldType::VarChar(100)),
2965 ],
2966 constraints: vec![],
2967 without_rowid: None,
2968 partition: None,
2969 interleave_in_parent: None,
2970 };
2971 create_op.state_forwards("testapp", &mut state);
2972
2973 let alter_op = Operation::AlterColumn {
2975 table: "test_table".to_string(),
2976 column: "name".to_string(),
2977 old_definition: None,
2978 new_definition: ColumnDefinition::new("name", FieldType::VarChar(255)),
2979 mysql_options: None,
2980 };
2981 alter_op.state_forwards("testapp", &mut state);
2982
2983 let model = state.get_model("testapp", "test_table").unwrap();
2984 assert!(model.fields.contains_key("name"));
2985 }
2986
2987 #[test]
2988 fn test_alter_field_1() {
2990 use crate::migrations::ProjectState;
2991 use crate::migrations::operations::*;
2992
2993 let mut state = ProjectState::new();
2994
2995 let create_op = Operation::CreateTable {
2996 name: "users".to_string(),
2997 columns: vec![
2998 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
2999 ColumnDefinition::new("email", FieldType::VarChar(100)),
3000 ],
3001 constraints: vec![],
3002 without_rowid: None,
3003 partition: None,
3004 interleave_in_parent: None,
3005 };
3006 create_op.state_forwards("app", &mut state);
3007
3008 let alter_op = Operation::AlterColumn {
3009 table: "users".to_string(),
3010 column: "email".to_string(),
3011 old_definition: None,
3012 new_definition: ColumnDefinition::new("email", FieldType::Text),
3013 mysql_options: None,
3014 };
3015 alter_op.state_forwards("app", &mut state);
3016
3017 assert!(
3018 state
3019 .get_model("app", "users")
3020 .unwrap()
3021 .fields
3022 .contains_key("email")
3023 );
3024 }
3025
3026 #[test]
3027 fn test_alter_field_and_unique_together() {
3029 use crate::migrations::ProjectState;
3030 use crate::migrations::operations::*;
3031
3032 let mut state = ProjectState::new();
3033
3034 let create_op = Operation::CreateTable {
3035 name: "items".to_string(),
3036 columns: vec![
3037 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3038 ColumnDefinition::new("code", FieldType::VarChar(50)),
3039 ColumnDefinition::new("category", FieldType::VarChar(50)),
3040 ],
3041 constraints: vec![],
3042 without_rowid: None,
3043 partition: None,
3044 interleave_in_parent: None,
3045 };
3046 create_op.state_forwards("app", &mut state);
3047
3048 let unique_op = Operation::AlterUniqueTogether {
3049 table: "items".to_string(),
3050 unique_together: vec![vec!["code".to_string(), "category".to_string()]],
3051 };
3052 unique_op.state_forwards("app", &mut state);
3053
3054 let alter_op = Operation::AlterColumn {
3055 table: "items".to_string(),
3056 column: "code".to_string(),
3057 old_definition: None,
3058 new_definition: ColumnDefinition::new("code", FieldType::VarChar(100)),
3059 mysql_options: None,
3060 };
3061 alter_op.state_forwards("app", &mut state);
3062
3063 assert!(state.get_model("app", "items").is_some());
3064 }
3065
3066 #[test]
3067 fn test_alter_field_and_unique_together_1() {
3069 use crate::migrations::ProjectState;
3070 use crate::migrations::operations::*;
3071
3072 let mut state = ProjectState::new();
3073
3074 let create_op = Operation::CreateTable {
3075 name: "orders".to_string(),
3076 columns: vec![
3077 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3078 ColumnDefinition::new("order_number", FieldType::VarChar(20)),
3079 ColumnDefinition::new("year", FieldType::Integer),
3080 ],
3081 constraints: vec![],
3082 without_rowid: None,
3083 partition: None,
3084 interleave_in_parent: None,
3085 };
3086 create_op.state_forwards("app", &mut state);
3087
3088 let unique_op = Operation::AlterUniqueTogether {
3089 table: "orders".to_string(),
3090 unique_together: vec![vec!["order_number".to_string(), "year".to_string()]],
3091 };
3092 unique_op.state_forwards("app", &mut state);
3093
3094 let alter_op = Operation::AlterColumn {
3095 table: "orders".to_string(),
3096 column: "year".to_string(),
3097 old_definition: None,
3098 new_definition: ColumnDefinition::new(
3099 "year",
3100 FieldType::Custom("SMALLINT".to_string()),
3101 ),
3102 mysql_options: None,
3103 };
3104 alter_op.state_forwards("app", &mut state);
3105
3106 assert!(state.get_model("app", "orders").is_some());
3107 }
3108
3109 #[test]
3110 fn test_alter_field_to_fk_dependency_other_app() {
3112 use crate::migrations::ProjectState;
3113 use crate::migrations::operations::*;
3114
3115 let mut state = ProjectState::new();
3116
3117 let create_users = Operation::CreateTable {
3119 name: "users".to_string(),
3120 columns: vec![ColumnDefinition::new(
3121 "id",
3122 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
3123 )],
3124 constraints: vec![],
3125 without_rowid: None,
3126 partition: None,
3127 interleave_in_parent: None,
3128 };
3129 create_users.state_forwards("auth_app", &mut state);
3130
3131 let create_posts = Operation::CreateTable {
3133 name: "posts".to_string(),
3134 columns: vec![
3135 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3136 ColumnDefinition::new("author_id", FieldType::Integer),
3137 ],
3138 constraints: vec![],
3139 without_rowid: None,
3140 partition: None,
3141 interleave_in_parent: None,
3142 };
3143 create_posts.state_forwards("blog_app", &mut state);
3144
3145 let alter_op = Operation::AlterColumn {
3147 table: "posts".to_string(),
3148 column: "author_id".to_string(),
3149 old_definition: None,
3150 new_definition: ColumnDefinition::new(
3151 "author_id",
3152 FieldType::Custom("INTEGER REFERENCES users(id)".to_string()),
3153 ),
3154 mysql_options: None,
3155 };
3156 alter_op.state_forwards("blog_app", &mut state);
3157
3158 assert!(
3159 state
3160 .get_model("blog_app", "posts")
3161 .unwrap()
3162 .fields
3163 .contains_key("author_id")
3164 );
3165 }
3166
3167 #[test]
3168 fn test_alter_field_to_fk_dependency_other_app_1() {
3170 use crate::migrations::ProjectState;
3171 use crate::migrations::operations::*;
3172
3173 let mut state = ProjectState::new();
3174
3175 let create_categories = Operation::CreateTable {
3176 name: "categories".to_string(),
3177 columns: vec![ColumnDefinition::new(
3178 "id",
3179 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
3180 )],
3181 constraints: vec![],
3182 without_rowid: None,
3183 partition: None,
3184 interleave_in_parent: None,
3185 };
3186 create_categories.state_forwards("catalog", &mut state);
3187
3188 let create_items = Operation::CreateTable {
3189 name: "items".to_string(),
3190 columns: vec![
3191 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3192 ColumnDefinition::new("cat_id", FieldType::Integer),
3193 ],
3194 constraints: vec![],
3195 without_rowid: None,
3196 partition: None,
3197 interleave_in_parent: None,
3198 };
3199 create_items.state_forwards("store", &mut state);
3200
3201 let alter_op = Operation::AlterColumn {
3202 table: "items".to_string(),
3203 column: "cat_id".to_string(),
3204 old_definition: None,
3205 new_definition: ColumnDefinition::new(
3206 "cat_id",
3207 FieldType::Custom("INTEGER REFERENCES categories(id)".to_string()),
3208 ),
3209 mysql_options: None,
3210 };
3211 alter_op.state_forwards("store", &mut state);
3212
3213 assert!(state.get_model("store", "items").is_some());
3214 }
3215
3216 #[test]
3217 fn test_alter_field_to_not_null_oneoff_default() {
3219 use crate::migrations::ProjectState;
3220 use crate::migrations::operations::*;
3221
3222 let mut state = ProjectState::new();
3223
3224 let create_op = Operation::CreateTable {
3225 name: "users".to_string(),
3226 columns: vec![
3227 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3228 ColumnDefinition::new("nickname", FieldType::VarChar(100)),
3229 ],
3230 constraints: vec![],
3231 without_rowid: None,
3232 partition: None,
3233 interleave_in_parent: None,
3234 };
3235 create_op.state_forwards("testapp", &mut state);
3236
3237 let alter_op = Operation::AlterColumn {
3242 table: "users".to_string(),
3243 column: "nickname".to_string(),
3244 old_definition: None,
3245 new_definition: ColumnDefinition::new(
3246 "nickname",
3247 FieldType::Custom("VARCHAR(100) NOT NULL".to_string()),
3248 ),
3249 mysql_options: None,
3250 };
3251 alter_op.state_forwards("testapp", &mut state);
3252
3253 assert!(
3254 state
3255 .get_model("testapp", "users")
3256 .unwrap()
3257 .fields
3258 .contains_key("nickname")
3259 );
3260 }
3261
3262 #[test]
3263 fn test_alter_field_to_not_null_oneoff_default_1() {
3265 use crate::migrations::ProjectState;
3266 use crate::migrations::operations::*;
3267
3268 let mut state = ProjectState::new();
3269
3270 let create_op = Operation::CreateTable {
3271 name: "profiles".to_string(),
3272 columns: vec![
3273 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3274 ColumnDefinition::new("bio", FieldType::Text),
3275 ],
3276 constraints: vec![],
3277 without_rowid: None,
3278 partition: None,
3279 interleave_in_parent: None,
3280 };
3281 create_op.state_forwards("app", &mut state);
3282
3283 let alter_op = Operation::AlterColumn {
3284 table: "profiles".to_string(),
3285 column: "bio".to_string(),
3286 old_definition: None,
3287 new_definition: ColumnDefinition::new(
3288 "bio",
3289 FieldType::Custom("TEXT NOT NULL".to_string()),
3290 ),
3291 mysql_options: None,
3292 };
3293 alter_op.state_forwards("app", &mut state);
3294
3295 assert!(
3296 state
3297 .get_model("app", "profiles")
3298 .unwrap()
3299 .fields
3300 .contains_key("bio")
3301 );
3302 }
3303
3304 #[test]
3305 fn test_alter_field_to_not_null_with_db_default() {
3307 use crate::migrations::ProjectState;
3308 use crate::migrations::operations::*;
3309
3310 let mut state = ProjectState::new();
3311
3312 let create_op = Operation::CreateTable {
3313 name: "products".to_string(),
3314 columns: vec![
3315 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3316 ColumnDefinition::new("quantity", FieldType::Integer),
3317 ],
3318 constraints: vec![],
3319 without_rowid: None,
3320 partition: None,
3321 interleave_in_parent: None,
3322 };
3323 create_op.state_forwards("testapp", &mut state);
3324
3325 let alter_op = Operation::AlterColumn {
3327 table: "products".to_string(),
3328 column: "quantity".to_string(),
3329 old_definition: None,
3330 new_definition: ColumnDefinition::new(
3331 "quantity",
3332 FieldType::Custom("INTEGER NOT NULL DEFAULT 0".to_string()),
3333 ),
3334 mysql_options: None,
3335 };
3336 alter_op.state_forwards("testapp", &mut state);
3337
3338 assert!(
3339 state
3340 .get_model("testapp", "products")
3341 .unwrap()
3342 .fields
3343 .contains_key("quantity")
3344 );
3345 }
3346
3347 #[test]
3348 fn test_alter_field_to_not_null_with_db_default_1() {
3350 use crate::migrations::ProjectState;
3351 use crate::migrations::operations::*;
3352
3353 let mut state = ProjectState::new();
3354
3355 let create_op = Operation::CreateTable {
3356 name: "items".to_string(),
3357 columns: vec![
3358 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3359 ColumnDefinition::new("available", FieldType::Boolean),
3360 ],
3361 constraints: vec![],
3362 without_rowid: None,
3363 partition: None,
3364 interleave_in_parent: None,
3365 };
3366 create_op.state_forwards("app", &mut state);
3367
3368 let alter_op = Operation::AlterColumn {
3369 table: "items".to_string(),
3370 column: "available".to_string(),
3371 old_definition: None,
3372 new_definition: ColumnDefinition::new(
3373 "available",
3374 FieldType::Custom("BOOLEAN NOT NULL DEFAULT TRUE".to_string()),
3375 ),
3376 mysql_options: None,
3377 };
3378 alter_op.state_forwards("app", &mut state);
3379
3380 assert!(
3381 state
3382 .get_model("app", "items")
3383 .unwrap()
3384 .fields
3385 .contains_key("available")
3386 );
3387 }
3388
3389 #[test]
3390 fn test_alter_field_to_not_null_with_default() {
3392 use crate::migrations::ProjectState;
3393 use crate::migrations::operations::*;
3394
3395 let mut state = ProjectState::new();
3396
3397 let create_op = Operation::CreateTable {
3398 name: "users".to_string(),
3399 columns: vec![
3400 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3401 ColumnDefinition::new("status", FieldType::VarChar(50)),
3402 ],
3403 constraints: vec![],
3404 without_rowid: None,
3405 partition: None,
3406 interleave_in_parent: None,
3407 };
3408 create_op.state_forwards("testapp", &mut state);
3409
3410 let alter_op = Operation::AlterColumn {
3412 table: "users".to_string(),
3413 column: "status".to_string(),
3414 old_definition: None,
3415 new_definition: ColumnDefinition::new(
3416 "status",
3417 FieldType::Custom("VARCHAR(50) NOT NULL DEFAULT 'active'".to_string()),
3418 ),
3419 mysql_options: None,
3420 };
3421 alter_op.state_forwards("testapp", &mut state);
3422
3423 assert!(
3424 state
3425 .get_model("testapp", "users")
3426 .unwrap()
3427 .fields
3428 .contains_key("status")
3429 );
3430 }
3431
3432 #[test]
3433 fn test_alter_field_to_not_null_with_default_1() {
3435 use crate::migrations::ProjectState;
3436 use crate::migrations::operations::*;
3437
3438 let mut state = ProjectState::new();
3439
3440 let create_op = Operation::CreateTable {
3441 name: "products".to_string(),
3442 columns: vec![
3443 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3444 ColumnDefinition::new("active", FieldType::Boolean),
3445 ],
3446 constraints: vec![],
3447 without_rowid: None,
3448 partition: None,
3449 interleave_in_parent: None,
3450 };
3451 create_op.state_forwards("app", &mut state);
3452
3453 let alter_op = Operation::AlterColumn {
3454 table: "products".to_string(),
3455 column: "active".to_string(),
3456 old_definition: None,
3457 new_definition: ColumnDefinition::new(
3458 "active",
3459 FieldType::Custom("BOOLEAN NOT NULL DEFAULT TRUE".to_string()),
3460 ),
3461 mysql_options: None,
3462 };
3463 alter_op.state_forwards("app", &mut state);
3464
3465 assert!(
3466 state
3467 .get_model("app", "products")
3468 .unwrap()
3469 .fields
3470 .contains_key("active")
3471 );
3472 }
3473
3474 #[test]
3475 fn test_alter_field_to_not_null_without_default() {
3477 use crate::migrations::ProjectState;
3478 use crate::migrations::operations::*;
3479
3480 let mut state = ProjectState::new();
3481
3482 let create_op = Operation::CreateTable {
3483 name: "users".to_string(),
3484 columns: vec![
3485 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3486 ColumnDefinition::new("email", FieldType::VarChar(255)),
3487 ],
3488 constraints: vec![],
3489 without_rowid: None,
3490 partition: None,
3491 interleave_in_parent: None,
3492 };
3493 create_op.state_forwards("testapp", &mut state);
3494
3495 let alter_op = Operation::AlterColumn {
3497 table: "users".to_string(),
3498 column: "email".to_string(),
3499 old_definition: None,
3500 new_definition: ColumnDefinition::new(
3501 "email",
3502 FieldType::Custom("VARCHAR(255) NOT NULL".to_string()),
3503 ),
3504 mysql_options: None,
3505 };
3506 alter_op.state_forwards("testapp", &mut state);
3507
3508 assert!(
3509 state
3510 .get_model("testapp", "users")
3511 .unwrap()
3512 .fields
3513 .contains_key("email")
3514 );
3515 }
3516
3517 #[test]
3518 fn test_alter_field_to_not_null_without_default_1() {
3520 use crate::migrations::ProjectState;
3521 use crate::migrations::operations::*;
3522
3523 let mut state = ProjectState::new();
3524
3525 let create_op = Operation::CreateTable {
3526 name: "orders".to_string(),
3527 columns: vec![
3528 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3529 ColumnDefinition::new("customer_id", FieldType::Integer),
3530 ],
3531 constraints: vec![],
3532 without_rowid: None,
3533 partition: None,
3534 interleave_in_parent: None,
3535 };
3536 create_op.state_forwards("app", &mut state);
3537
3538 let alter_op = Operation::AlterColumn {
3539 table: "orders".to_string(),
3540 column: "customer_id".to_string(),
3541 old_definition: None,
3542 new_definition: ColumnDefinition::new(
3543 "customer_id",
3544 FieldType::Custom("INTEGER NOT NULL".to_string()),
3545 ),
3546 mysql_options: None,
3547 };
3548 alter_op.state_forwards("app", &mut state);
3549
3550 assert!(
3551 state
3552 .get_model("app", "orders")
3553 .unwrap()
3554 .fields
3555 .contains_key("customer_id")
3556 );
3557 }
3558
3559 #[test]
3560 fn test_alter_fk_before_model_deletion() {
3562 use crate::migrations::ProjectState;
3563 use crate::migrations::operations::*;
3564
3565 let mut state = ProjectState::new();
3566
3567 let create_old = Operation::CreateTable {
3568 name: "old_table".to_string(),
3569 columns: vec![ColumnDefinition::new(
3570 "id",
3571 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
3572 )],
3573 constraints: vec![],
3574 without_rowid: None,
3575 partition: None,
3576 interleave_in_parent: None,
3577 };
3578 create_old.state_forwards("testapp", &mut state);
3579
3580 let create_new = Operation::CreateTable {
3581 name: "new_table".to_string(),
3582 columns: vec![ColumnDefinition::new(
3583 "id",
3584 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
3585 )],
3586 constraints: vec![],
3587 without_rowid: None,
3588 partition: None,
3589 interleave_in_parent: None,
3590 };
3591 create_new.state_forwards("testapp", &mut state);
3592
3593 let create_ref = Operation::CreateTable {
3594 name: "referencing".to_string(),
3595 columns: vec![
3596 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3597 ColumnDefinition::new(
3598 "ref_id",
3599 FieldType::Custom("INTEGER REFERENCES old_table(id)".to_string()),
3600 ),
3601 ],
3602 constraints: vec![],
3603 without_rowid: None,
3604 partition: None,
3605 interleave_in_parent: None,
3606 };
3607 create_ref.state_forwards("testapp", &mut state);
3608
3609 let alter_fk = Operation::AlterColumn {
3611 table: "referencing".to_string(),
3612 column: "ref_id".to_string(),
3613 old_definition: None,
3614 new_definition: ColumnDefinition::new(
3615 "ref_id",
3616 FieldType::Custom("INTEGER REFERENCES new_table(id)".to_string()),
3617 ),
3618 mysql_options: None,
3619 };
3620 alter_fk.state_forwards("testapp", &mut state);
3621
3622 let drop_old = Operation::DropTable {
3624 name: "old_table".to_string(),
3625 };
3626 drop_old.state_forwards("testapp", &mut state);
3627
3628 assert!(state.get_model("testapp", "old_table").is_none());
3629 assert!(state.get_model("testapp", "referencing").is_some());
3630 }
3631
3632 #[test]
3633 fn test_alter_fk_before_model_deletion_1() {
3635 use crate::migrations::ProjectState;
3636 use crate::migrations::operations::*;
3637
3638 let mut state = ProjectState::new();
3639
3640 let create_categories = Operation::CreateTable {
3641 name: "categories".to_string(),
3642 columns: vec![ColumnDefinition::new(
3643 "id",
3644 FieldType::Custom("INTEGER PRIMARY KEY".to_string()),
3645 )],
3646 constraints: vec![],
3647 without_rowid: None,
3648 partition: None,
3649 interleave_in_parent: None,
3650 };
3651 create_categories.state_forwards("app", &mut state);
3652
3653 let create_products = Operation::CreateTable {
3654 name: "products".to_string(),
3655 columns: vec![
3656 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3657 ColumnDefinition::new("cat_id", FieldType::Integer),
3658 ],
3659 constraints: vec![],
3660 without_rowid: None,
3661 partition: None,
3662 interleave_in_parent: None,
3663 };
3664 create_products.state_forwards("app", &mut state);
3665
3666 let alter_op = Operation::AlterColumn {
3668 table: "products".to_string(),
3669 column: "cat_id".to_string(),
3670 old_definition: None,
3671 new_definition: ColumnDefinition::new(
3672 "cat_id",
3673 FieldType::Custom("INTEGER NULL".to_string()),
3674 ),
3675 mysql_options: None,
3676 };
3677 alter_op.state_forwards("app", &mut state);
3678
3679 assert!(state.get_model("app", "products").is_some());
3680 }
3681
3682 #[test]
3683 fn test_alter_many_to_many() {
3685 use crate::migrations::ProjectState;
3687 use crate::migrations::operations::*;
3688
3689 let mut state = ProjectState::new();
3690
3691 let create_authors = Operation::CreateTable {
3693 name: "authors".to_string(),
3694 columns: vec![
3695 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3696 ColumnDefinition::new("name", FieldType::VarChar(100)),
3697 ],
3698 constraints: vec![],
3699 without_rowid: None,
3700 partition: None,
3701 interleave_in_parent: None,
3702 };
3703 create_authors.state_forwards("library", &mut state);
3704
3705 let create_books = Operation::CreateTable {
3706 name: "books".to_string(),
3707 columns: vec![
3708 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3709 ColumnDefinition::new("title", FieldType::VarChar(200)),
3710 ],
3711 constraints: vec![],
3712 without_rowid: None,
3713 partition: None,
3714 interleave_in_parent: None,
3715 };
3716 create_books.state_forwards("library", &mut state);
3717
3718 let create_assoc = Operation::CreateTable {
3720 name: "authors_books".to_string(),
3721 columns: vec![
3722 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3723 ColumnDefinition::new(
3724 "author_id",
3725 FieldType::Custom("INTEGER REFERENCES authors(id)".to_string()),
3726 ),
3727 ColumnDefinition::new(
3728 "book_id",
3729 FieldType::Custom("INTEGER REFERENCES books(id)".to_string()),
3730 ),
3731 ],
3732 constraints: vec![Constraint::Unique {
3733 name: "unique_author_book".to_string(),
3734 columns: vec!["author_id".to_string(), "book_id".to_string()],
3735 }],
3736 without_rowid: None,
3737 partition: None,
3738 interleave_in_parent: None,
3739 };
3740 create_assoc.state_forwards("library", &mut state);
3741
3742 let add_created_at = Operation::AddColumn {
3744 table: "authors_books".to_string(),
3745 column: ColumnDefinition::new(
3746 "created_at",
3747 FieldType::Custom(
3748 FieldType::Custom("TIMESTAMP DEFAULT CURRENT_TIMESTAMP".to_string())
3749 .to_string(),
3750 ),
3751 ),
3752 mysql_options: None,
3753 };
3754 add_created_at.state_forwards("library", &mut state);
3755
3756 let add_role = Operation::AddColumn {
3757 table: "authors_books".to_string(),
3758 column: ColumnDefinition::new("contribution_role", FieldType::VarChar(50)),
3759 mysql_options: None,
3760 };
3761 add_role.state_forwards("library", &mut state);
3762
3763 let assoc_model = state.get_model("library", "authors_books").unwrap();
3765 assert!(assoc_model.fields.contains_key("author_id"));
3766 assert!(assoc_model.fields.contains_key("book_id"));
3767 assert!(assoc_model.fields.contains_key("created_at"));
3768 assert!(assoc_model.fields.contains_key("contribution_role"));
3769 }
3770
3771 #[test]
3772 fn test_alter_many_to_many_1() {
3774 use crate::migrations::ProjectState;
3776 use crate::migrations::operations::*;
3777
3778 let mut state = ProjectState::new();
3779
3780 let create_students = Operation::CreateTable {
3782 name: "students".to_string(),
3783 columns: vec![
3784 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3785 ColumnDefinition::new("name", FieldType::VarChar(100)),
3786 ],
3787 constraints: vec![],
3788 without_rowid: None,
3789 partition: None,
3790 interleave_in_parent: None,
3791 };
3792 create_students.state_forwards("school", &mut state);
3793
3794 let create_courses = Operation::CreateTable {
3795 name: "courses".to_string(),
3796 columns: vec![
3797 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3798 ColumnDefinition::new("title", FieldType::VarChar(200)),
3799 ],
3800 constraints: vec![],
3801 without_rowid: None,
3802 partition: None,
3803 interleave_in_parent: None,
3804 };
3805 create_courses.state_forwards("school", &mut state);
3806
3807 let create_enrollment = Operation::CreateTable {
3809 name: "enrollments".to_string(),
3810 columns: vec![
3811 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3812 ColumnDefinition::new(
3813 "student_id",
3814 FieldType::Custom("INTEGER REFERENCES students(id)".to_string()),
3815 ),
3816 ColumnDefinition::new(
3817 "course_id",
3818 FieldType::Custom("INTEGER REFERENCES courses(id)".to_string()),
3819 ),
3820 ColumnDefinition::new("grade", FieldType::VarChar(2)),
3821 ],
3822 constraints: vec![],
3823 without_rowid: None,
3824 partition: None,
3825 interleave_in_parent: None,
3826 };
3827 create_enrollment.state_forwards("school", &mut state);
3828
3829 let alter_grade = Operation::AlterColumn {
3831 table: "enrollments".to_string(),
3832 column: "grade".to_string(),
3833 old_definition: None,
3834 new_definition: ColumnDefinition::new(
3835 "grade",
3836 FieldType::Decimal {
3837 precision: 3,
3838 scale: 2,
3839 },
3840 ),
3841 mysql_options: None,
3842 };
3843 alter_grade.state_forwards("school", &mut state);
3844
3845 let add_index = Operation::CreateIndex {
3847 table: "enrollments".to_string(),
3848 columns: vec!["student_id".to_string(), "course_id".to_string()],
3849 unique: true,
3850 index_type: None,
3851 where_clause: None,
3852 concurrently: false,
3853 expressions: None,
3854 mysql_options: None,
3855 operator_class: None,
3856 };
3857 add_index.state_forwards("school", &mut state);
3858
3859 let enrollment_model = state.get_model("school", "enrollments").unwrap();
3860 assert!(enrollment_model.fields.contains_key("student_id"));
3861 assert!(enrollment_model.fields.contains_key("course_id"));
3862 assert!(enrollment_model.fields.contains_key("grade"));
3863 }
3864
3865 #[test]
3866 #[should_panic(expected = "runtime-only")]
3867 fn test_alter_model_managers() {
3869 use crate::migrations::operations::Operation;
3875 let _ = std::any::type_name::<Operation>();
3876
3877 panic!(
3880 "Model managers are runtime-only and don't require migration support. See reinhardt-orm manager module"
3881 )
3882 }
3883
3884 #[test]
3885 #[should_panic(expected = "runtime-only")]
3886 fn test_alter_model_managers_1() {
3888 use crate::migrations::ProjectState;
3891 let _ = std::any::type_name::<ProjectState>();
3892
3893 panic!(
3895 "Model managers are runtime-only and don't require migration support. See reinhardt-orm manager module"
3896 )
3897 }
3898
3899 #[test]
3900 fn test_alter_model_options() {
3902 use crate::migrations::ProjectState;
3903 use crate::migrations::operations::*;
3904 use std::collections::HashMap;
3905
3906 let mut state = ProjectState::new();
3907
3908 let create_op = Operation::CreateTable {
3909 name: "articles".to_string(),
3910 columns: vec![
3911 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3912 ColumnDefinition::new("title", FieldType::VarChar(255)),
3913 ColumnDefinition::new("created_at", FieldType::Custom("TIMESTAMP".to_string())),
3914 ],
3915 constraints: vec![],
3916 without_rowid: None,
3917 partition: None,
3918 interleave_in_parent: None,
3919 };
3920 create_op.state_forwards("app", &mut state);
3921
3922 let mut options = HashMap::new();
3923 options.insert("ordering".to_string(), "created_at".to_string());
3924 options.insert("verbose_name".to_string(), "Article".to_string());
3925
3926 let alter_op = Operation::AlterModelOptions {
3927 table: "articles".to_string(),
3928 options,
3929 };
3930 alter_op.state_forwards("app", &mut state);
3931
3932 assert!(state.get_model("app", "articles").is_some());
3933 }
3934
3935 #[test]
3936 fn test_alter_model_options_1() {
3938 use crate::migrations::ProjectState;
3939 use crate::migrations::operations::*;
3940 use std::collections::HashMap;
3941
3942 let mut state = ProjectState::new();
3943
3944 let create_op = Operation::CreateTable {
3945 name: "products".to_string(),
3946 columns: vec![
3947 ColumnDefinition::new("id", FieldType::Custom("INTEGER PRIMARY KEY".to_string())),
3948 ColumnDefinition::new("name", FieldType::VarChar(255)),
3949 ColumnDefinition::new(
3950 "price",
3951 FieldType::Decimal {
3952 precision: 10,
3953 scale: 2,
3954 },
3955 ),
3956 ],
3957 constraints: vec![],
3958 without_rowid: None,
3959 partition: None,
3960 interleave_in_parent: None,
3961 };
3962 create_op.state_forwards("app", &mut state);
3963
3964 let mut options = HashMap::new();
3965 options.insert("ordering".to_string(), "-price".to_string());
3966 options.insert("verbose_name_plural".to_string(), "Products".to_string());
3967
3968 let alter_op = Operation::AlterModelOptions {
3969 table: "products".to_string(),
3970 options,
3971 };
3972 alter_op.state_forwards("app", &mut state);
3973
3974 assert!(state.get_model("app", "products").is_some());
3975 }
3976
3977 #[test]
3978 #[should_panic(expected = "don't affect database schema")]
3979 fn test_alter_model_options_proxy() {
3981 use super::Migration;
3988 let _ = std::any::type_name::<Migration>();
3989
3990 panic!("Proxy models don't require migrations as they don't affect database schema")
3994 }
3995
3996 #[test]
3997 #[should_panic(expected = "don't affect database schema")]
3998 fn test_alter_model_options_proxy_1() {
4000 use crate::migrations::ColumnDefinition;
4003 let _ = std::any::type_name::<ColumnDefinition>();
4004
4005 panic!("Proxy models don't require migrations as they don't affect database schema")
4007 }
4008
4009 #[test]
4010 fn test_alter_regex_string_to_compiled_regex() {
4012 use crate::migrations::ProjectState;
4020
4021 let state = ProjectState::new();
4022 assert!(state.models.is_empty());
4024 }
4025
4026 #[test]
4027 fn test_alter_regex_string_to_compiled_regex_1() {
4029 use crate::migrations::ProjectState;
4031
4032 let state = ProjectState::new();
4033 assert!(state.models.is_empty());
4035 }
4036}