@@ -68,6 +68,52 @@ fn make_package_migrations(
6868 Ok ( ( ) )
6969}
7070
71+ pub fn create_new_migration (
72+ path : & Path ,
73+ name : & str ,
74+ options : MigrationGeneratorOptions ,
75+ ) -> anyhow:: Result < ( ) > {
76+ let Some ( manager) = CargoTomlManager :: from_path ( path) ? else {
77+ bail ! ( "Cargo.toml not found in the specified directory or any parent directory." )
78+ } ;
79+
80+ match manager {
81+ CargoTomlManager :: Workspace ( workspace) => {
82+ let Some ( package) = workspace. get_current_package_manager ( ) else {
83+ bail ! (
84+ "Generating migrations for workspaces is not supported yet. \
85+ Please generate migrations for each package separately."
86+ ) ;
87+ } ;
88+ create_package_new_migration ( package, name, options)
89+ }
90+ CargoTomlManager :: Package ( package) => create_package_new_migration ( & package, name, options) ,
91+ }
92+ }
93+
94+ fn create_package_new_migration (
95+ manager : & PackageManager ,
96+ name : & str ,
97+ options : MigrationGeneratorOptions ,
98+ ) -> anyhow:: Result < ( ) > {
99+ let crate_name = manager. get_package_name ( ) . to_string ( ) ;
100+ let manifest_path = manager. get_manifest_path ( ) ;
101+
102+ let generator = MigrationGenerator :: new ( manifest_path, crate_name, options) ;
103+ let migration = generator
104+ . generate_custom_migration ( name)
105+ . context ( "unable to generate migration" ) ?;
106+
107+ generator
108+ . write_migrations ( & migration)
109+ . context ( "unable to write migrations" ) ?;
110+ generator
111+ . write_migrations_module ( )
112+ . context ( "unable to write migrations.rs" ) ?;
113+
114+ Ok ( ( ) )
115+ }
116+
71117pub fn list_migrations ( path : & Path ) -> anyhow:: Result < HashMap < String , Vec < String > > > {
72118 if let Some ( manager) = CargoTomlManager :: from_path ( path) ? {
73119 let mut migration_list = HashMap :: new ( ) ;
@@ -157,7 +203,7 @@ impl MigrationGenerator {
157203 if operations. is_empty ( ) {
158204 Ok ( None )
159205 } else {
160- let migration_name = migration_processor. next_migration_name ( ) ?;
206+ let migration_name = migration_processor. next_auto_migration_name ( ) ?;
161207 let dependencies = migration_processor. base_dependencies ( ) ;
162208
163209 let migration =
@@ -166,6 +212,60 @@ impl MigrationGenerator {
166212 }
167213 }
168214
215+ pub fn generate_custom_migration ( & self , name : & str ) -> anyhow:: Result < MigrationAsSource > {
216+ let source_files = self . get_source_files ( ) ?;
217+ self . generate_custom_migration_from_files ( name, source_files)
218+ }
219+
220+ pub fn generate_custom_migration_from_files (
221+ & self ,
222+ name : & str ,
223+ source_files : Vec < SourceFile > ,
224+ ) -> anyhow:: Result < MigrationAsSource > {
225+ let AppState { migrations, .. } = self . process_source_files ( source_files) ?;
226+ let migration_processor = MigrationProcessor :: new ( migrations) ?;
227+
228+ let migration_name = migration_processor. next_migration_name_with_suffix ( name) ?;
229+ let dependencies = migration_processor. base_dependencies ( ) ;
230+
231+ let dependencies_repr: Vec < _ > = dependencies. iter ( ) . map ( Repr :: repr) . collect ( ) ;
232+
233+ let app_name = self . options . app_name . as_ref ( ) . unwrap_or ( & self . crate_name ) ;
234+
235+ let migration_def = quote ! {
236+ #[ derive( Debug , Copy , Clone ) ]
237+ pub ( super ) struct Migration ;
238+
239+ impl :: cot:: db:: migrations:: Migration for Migration {
240+ const APP_NAME : & ' static str = #app_name;
241+ const MIGRATION_NAME : & ' static str = #migration_name;
242+ const DEPENDENCIES : & ' static [ :: cot:: db:: migrations:: MigrationDependency ] = & [
243+ #( #dependencies_repr, ) *
244+ ] ;
245+ const OPERATIONS : & ' static [ :: cot:: db:: migrations:: Operation ] = & [
246+ :: cot:: db:: migrations:: Operation :: custom( forwards) . backwards( backwards) . build( ) ,
247+ ] ;
248+ }
249+
250+ #[ :: cot:: db:: migrations:: migration_op]
251+ async fn forwards( _ctx: :: cot:: db:: migrations:: MigrationContext <' _>) -> :: cot:: db:: Result <( ) > {
252+ Ok ( ( ) )
253+ }
254+
255+ #[ :: cot:: db:: migrations:: migration_op]
256+ async fn backwards( _ctx: :: cot:: db:: migrations:: MigrationContext <' _>) -> :: cot:: db:: Result <( ) > {
257+ Err ( :: cot:: db:: DatabaseError :: MigrationError (
258+ :: cot:: db:: migrations:: MigrationEngineError :: Custom ( "Backwards migration not implemented" . into( ) )
259+ ) )
260+ }
261+ } ;
262+
263+ Ok ( MigrationAsSource :: new (
264+ migration_name,
265+ Self :: generate_migration ( migration_def, TokenStream :: new ( ) ) ,
266+ ) )
267+ }
268+
169269 pub fn write_migrations ( & self , migration : & MigrationAsSource ) -> anyhow:: Result < ( ) > {
170270 print_status_msg (
171271 StatusType :: Creating ,
@@ -847,11 +947,32 @@ impl MigrationProcessor {
847947 migration_models. into_values ( ) . cloned ( ) . collect ( )
848948 }
849949
850- fn next_migration_name ( & self ) -> anyhow:: Result < String > {
950+ fn next_auto_migration_name ( & self ) -> anyhow:: Result < String > {
851951 if self . migrations . is_empty ( ) {
852952 return Ok ( format ! ( "{MIGRATIONS_MODULE_PREFIX}0001_initial" ) ) ;
853953 }
854954
955+ let migration_number = self . get_next_migration_number ( ) ?;
956+ let now = chrono:: Utc :: now ( ) ;
957+ let date_time = now. format ( "%Y%m%d_%H%M%S" ) ;
958+
959+ Ok ( format ! (
960+ "{MIGRATIONS_MODULE_PREFIX}{migration_number:04}_auto_{date_time}"
961+ ) )
962+ }
963+
964+ fn next_migration_name_with_suffix ( & self , suffix : & str ) -> anyhow:: Result < String > {
965+ let migration_number = self . get_next_migration_number ( ) ?;
966+ Ok ( format ! (
967+ "{MIGRATIONS_MODULE_PREFIX}{migration_number:04}_{suffix}"
968+ ) )
969+ }
970+
971+ fn get_next_migration_number ( & self ) -> anyhow:: Result < u32 > {
972+ if self . migrations . is_empty ( ) {
973+ return Ok ( 1 ) ;
974+ }
975+
855976 let last_migration = self . migrations . last ( ) . unwrap ( ) ;
856977 let last_migration_number = last_migration
857978 . name
@@ -871,13 +992,7 @@ impl MigrationProcessor {
871992 )
872993 } ) ?;
873994
874- let migration_number = last_migration_number + 1 ;
875- let now = chrono:: Utc :: now ( ) ;
876- let date_time = now. format ( "%Y%m%d_%H%M%S" ) ;
877-
878- Ok ( format ! (
879- "{MIGRATIONS_MODULE_PREFIX}{migration_number:04}_auto_{date_time}"
880- ) )
995+ Ok ( last_migration_number + 1 )
881996 }
882997
883998 /// Returns the list of dependencies for the next migration, based on the
@@ -1441,7 +1556,7 @@ mod tests {
14411556 let migrations = vec ! [ ] ;
14421557 let processor = MigrationProcessor :: new ( migrations) . unwrap ( ) ;
14431558
1444- let next_migration_name = processor. next_migration_name ( ) . unwrap ( ) ;
1559+ let next_migration_name = processor. next_auto_migration_name ( ) . unwrap ( ) ;
14451560 assert_eq ! ( next_migration_name, "m_0001_initial" ) ;
14461561 }
14471562
@@ -1473,6 +1588,42 @@ mod tests {
14731588 ) ;
14741589 }
14751590
1591+ #[ test]
1592+ fn migration_processor_next_migration_name_with_suffix ( ) {
1593+ let migrations = vec ! [ Migration {
1594+ app_name: "app1" . to_string( ) ,
1595+ name: "m_0001_initial" . to_string( ) ,
1596+ models: vec![ ] ,
1597+ } ] ;
1598+ let processor = MigrationProcessor :: new ( migrations) . unwrap ( ) ;
1599+
1600+ let next_name = processor. next_migration_name_with_suffix ( "custom" ) . unwrap ( ) ;
1601+ assert_eq ! ( next_name, "m_0002_custom" ) ;
1602+ }
1603+
1604+ #[ test]
1605+ fn create_new_migration_check_files_exist ( ) {
1606+ let tempdir = tempfile:: tempdir ( ) . unwrap ( ) ;
1607+ let cargo_toml_path = tempdir. path ( ) . join ( "Cargo.toml" ) ;
1608+ std:: fs:: create_dir ( tempdir. path ( ) . join ( "src" ) ) . unwrap ( ) ;
1609+ std:: fs:: write (
1610+ & cargo_toml_path,
1611+ "[package]\n name = \" testapp\" \n version = \" 0.1.0\" \n edition = \" 2021\" " ,
1612+ )
1613+ . unwrap ( ) ;
1614+
1615+ let options = MigrationGeneratorOptions :: default ( ) ;
1616+ create_new_migration ( tempdir. path ( ) , "my_custom" , options) . unwrap ( ) ;
1617+
1618+ let migration_file = tempdir. path ( ) . join ( "src/migrations/m_0001_my_custom.rs" ) ;
1619+ assert ! ( migration_file. exists( ) ) ;
1620+
1621+ let migrations_mod = tempdir. path ( ) . join ( "src/migrations.rs" ) ;
1622+ assert ! ( migrations_mod. exists( ) ) ;
1623+ let contents = std:: fs:: read_to_string ( migrations_mod) . unwrap ( ) ;
1624+ assert ! ( contents. contains( "pub mod m_0001_my_custom;" ) ) ;
1625+ }
1626+
14761627 #[ test]
14771628 fn toposort_operations ( ) {
14781629 let mut operations = vec ! [
0 commit comments