@@ -716,9 +716,9 @@ impl MigrationOperationGenerator {
716716
717717 #[ must_use]
718718 fn make_alter_field_operation (
719- _app_model : & ModelInSource ,
719+ app_model : & ModelInSource ,
720720 app_field : & Field ,
721- migration_model : & ModelInSource ,
721+ _migration_model : & ModelInSource ,
722722 migration_field : & Field ,
723723 ) -> Option < DynOperation > {
724724 if app_field == migration_field {
@@ -728,20 +728,15 @@ impl MigrationOperationGenerator {
728728 StatusType :: Modifying ,
729729 & format ! (
730730 "Field '{}' from Model '{}'" ,
731- & migration_field. name, migration_model. model. name
732- ) ,
733- ) ;
734-
735- todo ! ( ) ;
736-
737- #[ expect( unreachable_code) ]
738- print_status_msg (
739- StatusType :: Modified ,
740- & format ! (
741- "Field '{}' from Model '{}'" ,
742- & migration_field. name, migration_model. model. name
731+ & migration_field. name, app_model. model. name
743732 ) ,
744733 ) ;
734+ Some ( DynOperation :: AlterField {
735+ table_name : app_model. model . table_name . clone ( ) ,
736+ model_ty : app_model. model . resolved_ty . clone ( ) ,
737+ old_field : Box :: new ( migration_field. clone ( ) ) ,
738+ new_field : Box :: new ( app_field. clone ( ) ) ,
739+ } )
745740 }
746741
747742 #[ must_use]
@@ -1009,24 +1004,22 @@ impl GeneratedMigration {
10091004 } => {
10101005 let to_type = match to {
10111006 DynOperation :: CreateModel { model_ty, .. } => model_ty,
1012- DynOperation :: AddField { .. } => {
1013- unreachable ! (
1014- "AddField operation shouldn't be a dependency of CreateModel \
1015- because it doesn't create a new model"
1016- )
1017- }
1018- DynOperation :: RemoveField { .. } => {
1019- unreachable ! (
1020- "RemoveField operation shouldn't be a dependency of CreateModel \
1021- because it doesn't create a new model"
1022- )
1023- }
1024- DynOperation :: RemoveModel { .. } => {
1025- unreachable ! (
1026- "RemoveModel operation shouldn't be a dependency of CreateModel \
1027- because it doesn't create a new model"
1028- )
1029- }
1007+ DynOperation :: AddField { .. } => unreachable ! (
1008+ "AddField operation shouldn't be a dependency of CreateModel \
1009+ because it doesn't create a new model"
1010+ ) ,
1011+ DynOperation :: RemoveField { .. } => unreachable ! (
1012+ "RemoveField operation shouldn't be a dependency of CreateModel \
1013+ because it doesn't create a new model"
1014+ ) ,
1015+ DynOperation :: RemoveModel { .. } => unreachable ! (
1016+ "RemoveModel operation shouldn't be a dependency of CreateModel \
1017+ because it doesn't create a new model"
1018+ ) ,
1019+ DynOperation :: AlterField { .. } => unreachable ! (
1020+ "AlterField operation shouldn't be a dependency of CreateModel \
1021+ because it doesn't create a new model"
1022+ ) ,
10301023 } ;
10311024 trace ! (
10321025 "Removing foreign keys from {} to {}" ,
@@ -1063,6 +1056,11 @@ impl GeneratedMigration {
10631056 // RemoveModel doesn't create dependencies, it only removes a model
10641057 unreachable ! ( "RemoveModel operation should never create cycles" )
10651058 }
1059+ DynOperation :: AlterField { .. } => {
1060+ // AlterField only changes metadata of an existing field,
1061+ // so it does not create dependency cycles.
1062+ unreachable ! ( "AlterField operation should never create cycles" )
1063+ }
10661064 }
10671065 }
10681066
@@ -1161,6 +1159,18 @@ impl GeneratedMigration {
11611159 // RemoveField Doesnt Add Foreign Keys
11621160 Vec :: new ( )
11631161 }
1162+ DynOperation :: AlterField {
1163+ new_field,
1164+ model_ty,
1165+ ..
1166+ } => {
1167+ let mut ops = vec ! [ ( i, model_ty. clone( ) ) ] ;
1168+ // Only depend on the new foreign key, not the old one
1169+ if let Some ( to_type) = foreign_key_for_field ( new_field) {
1170+ ops. push ( ( i, to_type) ) ;
1171+ }
1172+ ops
1173+ }
11641174 DynOperation :: RemoveModel { .. } => {
11651175 // RemoveModel Doesnt Add Foreign Keys
11661176 Vec :: new ( )
@@ -1317,6 +1327,12 @@ pub enum DynOperation {
13171327 model_ty : syn:: Type ,
13181328 fields : Vec < Field > ,
13191329 } ,
1330+ AlterField {
1331+ table_name : String ,
1332+ model_ty : syn:: Type ,
1333+ old_field : Box < Field > ,
1334+ new_field : Box < Field > ,
1335+ } ,
13201336}
13211337
13221338/// Returns whether given [`Field`] is a foreign key to given type.
@@ -1371,6 +1387,22 @@ impl Repr for DynOperation {
13711387 . build( )
13721388 }
13731389 }
1390+ Self :: AlterField {
1391+ table_name,
1392+ old_field,
1393+ new_field,
1394+ ..
1395+ } => {
1396+ let old_field = old_field. repr ( ) ;
1397+ let new_field = new_field. repr ( ) ;
1398+ quote ! {
1399+ :: cot:: db:: migrations:: Operation :: alter_field( )
1400+ . table_name( :: cot:: db:: Identifier :: new( #table_name) )
1401+ . old_field( #old_field)
1402+ . new_field( #new_field)
1403+ . build( )
1404+ }
1405+ }
13741406 Self :: RemoveModel {
13751407 table_name, fields, ..
13761408 } => {
@@ -2053,4 +2085,116 @@ mod tests {
20532085 panic ! ( "Expected a function item" ) ;
20542086 }
20552087 }
2088+
2089+ #[ test]
2090+ fn make_alter_field_operation ( ) {
2091+ let migration_model = get_test_model ( ) ;
2092+ let mut app_model = migration_model. clone ( ) ;
2093+
2094+ app_model. model . fields [ 0 ] . ty = parse_quote ! ( i32 ) ;
2095+
2096+ let migration_field = & migration_model. model . fields [ 0 ] ;
2097+ let app_field = & app_model. model . fields [ 0 ] ;
2098+
2099+ let operation = MigrationOperationGenerator :: make_alter_field_operation (
2100+ & app_model,
2101+ app_field,
2102+ & migration_model,
2103+ migration_field,
2104+ ) ;
2105+
2106+ match & operation {
2107+ Some ( DynOperation :: AlterField {
2108+ table_name,
2109+ model_ty,
2110+ old_field,
2111+ new_field,
2112+ } ) => {
2113+ assert_eq ! ( table_name, "test_model" ) ;
2114+ assert_eq ! ( model_ty, & parse_quote!( TestModel ) ) ;
2115+ assert_eq ! ( old_field. column_name, "field1" ) ;
2116+ assert_eq ! ( old_field. ty, parse_quote!( String ) ) ;
2117+ assert_eq ! ( new_field. column_name, "field1" ) ;
2118+ assert_eq ! ( new_field. ty, parse_quote!( i32 ) ) ;
2119+ }
2120+ _ => panic ! ( "Expected Some(DynOperation::AlterField)" ) ,
2121+ }
2122+ }
2123+
2124+ #[ test]
2125+ fn generate_operations_with_altered_field ( ) {
2126+ let migration_model = get_test_model ( ) ;
2127+ let mut app_model = migration_model. clone ( ) ;
2128+
2129+ app_model. model . fields [ 0 ] . ty = parse_quote ! ( i32 ) ;
2130+
2131+ let app_models = vec ! [ app_model. clone( ) ] ;
2132+ let migration_models = vec ! [ migration_model. clone( ) ] ;
2133+
2134+ let ( modified_models, operations) =
2135+ MigrationGenerator :: generate_operations ( & app_models, & migration_models) ;
2136+
2137+ assert_eq ! ( modified_models. len( ) , 1 ) ;
2138+ assert ! (
2139+ operations. iter( ) . any( |op| match op {
2140+ DynOperation :: AlterField {
2141+ old_field,
2142+ new_field,
2143+ ..
2144+ } => old_field. ty == parse_quote!( String ) && new_field. ty == parse_quote!( i32 ) ,
2145+ _ => false ,
2146+ } ) ,
2147+ "Expected an AlterField operation for changed type"
2148+ ) ;
2149+ }
2150+
2151+ #[ test]
2152+ fn repr_for_alter_field_operation ( ) {
2153+ let op = DynOperation :: AlterField {
2154+ table_name : "test_table" . to_string ( ) ,
2155+ model_ty : parse_quote ! ( TestModel ) ,
2156+ old_field : Box :: new ( Field {
2157+ name : format_ident ! ( "test_field" ) ,
2158+ column_name : "test_field" . to_string ( ) ,
2159+ ty : parse_quote ! ( String ) ,
2160+ auto_value : false ,
2161+ primary_key : false ,
2162+ unique : false ,
2163+ foreign_key : None ,
2164+ } ) ,
2165+ new_field : Box :: new ( Field {
2166+ name : format_ident ! ( "test_field" ) ,
2167+ column_name : "test_field" . to_string ( ) ,
2168+ ty : parse_quote ! ( i32 ) ,
2169+ auto_value : false ,
2170+ primary_key : false ,
2171+ unique : false ,
2172+ foreign_key : None ,
2173+ } ) ,
2174+ } ;
2175+
2176+ let tokens = op. repr ( ) ;
2177+ let tokens_str = tokens. to_string ( ) ;
2178+
2179+ assert ! (
2180+ tokens_str. contains( "alter_field" ) ,
2181+ "Should call alter_field() but got: {tokens_str}"
2182+ ) ;
2183+ assert ! (
2184+ tokens_str. contains( "table_name" ) ,
2185+ "Should call table_name() but got: {tokens_str}"
2186+ ) ;
2187+ assert ! (
2188+ tokens_str. contains( "old_field" ) ,
2189+ "Should call old_field() but got: {tokens_str}"
2190+ ) ;
2191+ assert ! (
2192+ tokens_str. contains( "new_field" ) ,
2193+ "Should call new_field() but got: {tokens_str}"
2194+ ) ;
2195+ assert ! (
2196+ tokens_str. contains( "build" ) ,
2197+ "Should call build() but got: {tokens_str}"
2198+ ) ;
2199+ }
20562200}
0 commit comments