@@ -48,6 +48,7 @@ pub fn make_migrations(path: &Path, options: MigrationGeneratorOptions) -> anyho
48
48
49
49
#[ derive( Debug , Clone , Default ) ]
50
50
pub struct MigrationGeneratorOptions {
51
+ pub app_name : Option < String > ,
51
52
pub output_dir : Option < PathBuf > ,
52
53
}
53
54
@@ -82,6 +83,7 @@ impl MigrationGenerator {
82
83
Ok ( ( ) )
83
84
}
84
85
86
+ /// Generate migrations as a ready-to-write source code.
85
87
pub fn generate_migrations_to_write (
86
88
& mut self ,
87
89
source_files : Vec < SourceFile > ,
@@ -95,6 +97,8 @@ impl MigrationGenerator {
95
97
}
96
98
}
97
99
100
+ /// Generate migrations and return internal structures that can be used to
101
+ /// generate source code.
98
102
pub fn generate_migrations (
99
103
& mut self ,
100
104
source_files : Vec < SourceFile > ,
@@ -319,7 +323,10 @@ impl MigrationGenerator {
319
323
fn make_create_model_operation ( app_model : & ModelInSource ) -> DynOperation {
320
324
DynOperation :: CreateModel {
321
325
table_name : app_model. model . table_name . clone ( ) ,
322
- model_ty : app_model. model . resolved_ty . clone ( ) . expect ( "resolved_ty is expected to be present when parsing the entire file with symbol resolver" ) ,
326
+ model_ty : app_model. model . resolved_ty . clone ( ) . expect (
327
+ "resolved_ty is expected to be present when \
328
+ parsing the entire file with symbol resolver",
329
+ ) ,
323
330
fields : app_model. model . fields . clone ( ) ,
324
331
}
325
332
}
@@ -381,7 +388,10 @@ impl MigrationGenerator {
381
388
fn make_add_field_operation ( app_model : & ModelInSource , field : & Field ) -> DynOperation {
382
389
DynOperation :: AddField {
383
390
table_name : app_model. model . table_name . clone ( ) ,
384
- model_ty : app_model. model . resolved_ty . clone ( ) . expect ( "resolved_ty is expected to be present when parsing the entire file with symbol resolver" ) ,
391
+ model_ty : app_model. model . resolved_ty . clone ( ) . expect (
392
+ "resolved_ty is expected to be present \
393
+ when parsing the entire file with symbol resolver",
394
+ ) ,
385
395
field : field. clone ( ) ,
386
396
}
387
397
}
@@ -426,7 +436,7 @@ impl MigrationGenerator {
426
436
. map ( |dependency| dependency. repr ( ) )
427
437
. collect ( ) ;
428
438
429
- let app_name = & self . crate_name ;
439
+ let app_name = self . options . app_name . as_ref ( ) . unwrap_or ( & self . crate_name ) ;
430
440
let migration_name = & migration. migration_name ;
431
441
let migration_def = quote ! {
432
442
#[ derive( Debug , Copy , Clone ) ]
@@ -666,6 +676,8 @@ pub struct GeneratedMigration {
666
676
}
667
677
668
678
impl GeneratedMigration {
679
+ /// Get the list of [`DynDependency`] for all foreign keys that point
680
+ /// to models that are **not** created in this migration.
669
681
fn get_foreign_key_dependencies ( & self ) -> Vec < DynDependency > {
670
682
let create_ops = self . get_create_ops_map ( ) ;
671
683
let ops_adding_foreign_keys = self . get_ops_adding_foreign_keys ( ) ;
@@ -682,6 +694,16 @@ impl GeneratedMigration {
682
694
dependencies
683
695
}
684
696
697
+ /// Removes dependency cycles by removing operations that create cycles.
698
+ ///
699
+ /// This method tries to minimize the number of operations added by
700
+ /// calculating the minimum feedback arc set of the dependency graph.
701
+ ///
702
+ /// This method modifies the `operations` field in place.
703
+ ///
704
+ /// # See also
705
+ ///
706
+ /// * [`Self::remove_dependency`]
685
707
fn remove_cycles ( & mut self ) {
686
708
let graph = self . construct_dependency_graph ( ) ;
687
709
@@ -701,6 +723,11 @@ impl GeneratedMigration {
701
723
}
702
724
}
703
725
726
+ /// Remove a dependency between two operations.
727
+ ///
728
+ /// This is done by removing foreign keys from the `from` operation that
729
+ /// point to the model created by `to` operation, and creating a new
730
+ /// `AddField` operation for each removed foreign key.
704
731
#[ must_use]
705
732
fn remove_dependency ( from : & mut DynOperation , to : & DynOperation ) -> Vec < DynOperation > {
706
733
match from {
@@ -712,7 +739,10 @@ impl GeneratedMigration {
712
739
let to_type = match to {
713
740
DynOperation :: CreateModel { model_ty, .. } => model_ty,
714
741
DynOperation :: AddField { .. } => {
715
- unreachable ! ( "AddField operation shouldn't be a dependency of CreateModel because it doesn't create a new model" )
742
+ unreachable ! (
743
+ "AddField operation shouldn't be a dependency of CreateModel \
744
+ because it doesn't create a new model"
745
+ )
716
746
}
717
747
} ;
718
748
trace ! (
@@ -745,18 +775,36 @@ impl GeneratedMigration {
745
775
}
746
776
}
747
777
778
+ /// Topologically sort operations in this migration.
779
+ ///
780
+ /// This is to ensure that operations will be applied in the correct order.
781
+ /// If there are no dependencies between operations, the order of operations
782
+ /// will not be modified.
783
+ ///
784
+ /// This method modifies the `operations` field in place.
785
+ ///
786
+ /// # Panics
787
+ ///
788
+ /// This method should be called after removing cycles; otherwise it will
789
+ /// panic.
748
790
fn toposort_operations ( & mut self ) {
749
791
let graph = self . construct_dependency_graph ( ) ;
750
792
751
793
let sorted = petgraph:: algo:: toposort ( & graph, None )
752
794
. expect ( "cycles shouldn't exist after removing them" ) ;
753
795
let mut sorted = sorted
754
796
. into_iter ( )
755
- . map ( petgraph:: prelude :: NodeIndex :: index)
797
+ . map ( petgraph:: graph :: NodeIndex :: index)
756
798
. collect :: < Vec < _ > > ( ) ;
757
799
flareon:: __private:: apply_permutation ( & mut self . operations , & mut sorted) ;
758
800
}
759
801
802
+ /// Construct a graph that represents reverse dependencies between
803
+ /// operations in this migration.
804
+ ///
805
+ /// The graph is directed and has an edge from operation A to operation B
806
+ /// if operation B creates a foreign key that points to a model created by
807
+ /// operation A.
760
808
#[ must_use]
761
809
fn construct_dependency_graph ( & mut self ) -> DiGraph < usize , ( ) , usize > {
762
810
let create_ops = self . get_create_ops_map ( ) ;
@@ -769,7 +817,11 @@ impl GeneratedMigration {
769
817
}
770
818
for ( i, dependency_ty) in & ops_adding_foreign_keys {
771
819
if let Some ( & dependency) = create_ops. get ( dependency_ty) {
772
- graph. add_edge ( NodeIndex :: new ( dependency) , NodeIndex :: new ( * i) , ( ) ) ;
820
+ graph. add_edge (
821
+ petgraph:: graph:: NodeIndex :: new ( dependency) ,
822
+ petgraph:: graph:: NodeIndex :: new ( * i) ,
823
+ ( ) ,
824
+ ) ;
773
825
}
774
826
}
775
827
@@ -855,16 +907,19 @@ impl Repr for Field {
855
907
let mut tokens = quote ! {
856
908
:: flareon:: db:: migrations:: Field :: new( :: flareon:: db:: Identifier :: new( #column_name) , <#ty as :: flareon:: db:: DatabaseField >:: TYPE )
857
909
} ;
858
- if self
859
- . auto_value
860
- . expect ( "auto_value is expected to be present when parsing the entire file with symbol resolver")
861
- {
910
+ if self . auto_value . expect (
911
+ " auto_value is expected to be present \
912
+ when parsing the entire file with symbol resolver",
913
+ ) {
862
914
tokens = quote ! { #tokens. auto( ) }
863
915
}
864
916
if self . primary_key {
865
917
tokens = quote ! { #tokens. primary_key( ) }
866
918
}
867
- if let Some ( fk_spec) = self . foreign_key . clone ( ) . expect ( "foreign_key is expected to be present when parsing the entire file with symbol resolver" ) {
919
+ if let Some ( fk_spec) = self . foreign_key . clone ( ) . expect (
920
+ "foreign_key is expected to be present \
921
+ when parsing the entire file with symbol resolver",
922
+ ) {
868
923
let to_model = & fk_spec. to_model ;
869
924
870
925
tokens = quote ! {
@@ -966,7 +1021,8 @@ fn is_field_foreign_key_to(field: &Field, ty: &syn::Type) -> bool {
966
1021
/// Returns [`None`] if the field is not a foreign key.
967
1022
fn foreign_key_for_field ( field : & Field ) -> Option < syn:: Type > {
968
1023
match field. foreign_key . clone ( ) . expect (
969
- "foreign_key is expected to be present when parsing the entire file with symbol resolver" ,
1024
+ "foreign_key is expected to be present \
1025
+ when parsing the entire file with symbol resolver",
970
1026
) {
971
1027
None => None ,
972
1028
Some ( foreign_key_spec) => Some ( foreign_key_spec. to_model ) ,
@@ -1339,4 +1395,59 @@ mod tests {
1339
1395
model_type: parse_quote!( crate :: Table4 ) ,
1340
1396
} ) ) ;
1341
1397
}
1398
+
1399
+ #[ test]
1400
+ fn make_add_field_operation ( ) {
1401
+ let app_model = ModelInSource {
1402
+ model_item : parse_quote ! {
1403
+ struct TestModel {
1404
+ id: i32 ,
1405
+ field1: i32 ,
1406
+ }
1407
+ } ,
1408
+ model : Model {
1409
+ name : format_ident ! ( "TestModel" ) ,
1410
+ original_name : "TestModel" . to_string ( ) ,
1411
+ resolved_ty : Some ( parse_quote ! ( TestModel ) ) ,
1412
+ model_type : Default :: default ( ) ,
1413
+ table_name : "test_model" . to_string ( ) ,
1414
+ pk_field : Field {
1415
+ field_name : format_ident ! ( "id" ) ,
1416
+ column_name : "id" . to_string ( ) ,
1417
+ ty : parse_quote ! ( i32 ) ,
1418
+ auto_value : MaybeUnknown :: Known ( true ) ,
1419
+ primary_key : true ,
1420
+ unique : false ,
1421
+ foreign_key : MaybeUnknown :: Known ( None ) ,
1422
+ } ,
1423
+ fields : vec ! [ ] ,
1424
+ } ,
1425
+ } ;
1426
+
1427
+ let field = Field {
1428
+ field_name : format_ident ! ( "new_field" ) ,
1429
+ column_name : "new_field" . to_string ( ) ,
1430
+ ty : parse_quote ! ( i32 ) ,
1431
+ auto_value : MaybeUnknown :: Known ( false ) ,
1432
+ primary_key : false ,
1433
+ unique : false ,
1434
+ foreign_key : MaybeUnknown :: Known ( None ) ,
1435
+ } ;
1436
+
1437
+ let operation = MigrationGenerator :: make_add_field_operation ( & app_model, & field) ;
1438
+
1439
+ match operation {
1440
+ DynOperation :: AddField {
1441
+ table_name,
1442
+ model_ty,
1443
+ field : op_field,
1444
+ } => {
1445
+ assert_eq ! ( table_name, "test_model" ) ;
1446
+ assert_eq ! ( model_ty, parse_quote!( TestModel ) ) ;
1447
+ assert_eq ! ( op_field. column_name, "new_field" ) ;
1448
+ assert_eq ! ( op_field. ty, parse_quote!( i32 ) ) ;
1449
+ }
1450
+ _ => panic ! ( "Expected AddField operation" ) ,
1451
+ }
1452
+ }
1342
1453
}
0 commit comments