@@ -11,17 +11,19 @@ use vespertide_loader::{
1111 load_config_or_default, load_migrations_at_compile_time, load_models_at_compile_time,
1212} ;
1313use vespertide_planner:: apply_action;
14- use vespertide_query:: { DatabaseBackend , build_plan_queries } ;
14+ use vespertide_query:: { build_plan_queries , DatabaseBackend } ;
1515
1616struct MacroInput {
1717 pool : Expr ,
1818 version_table : Option < String > ,
19+ verbose : bool ,
1920}
2021
2122impl Parse for MacroInput {
2223 fn parse ( input : ParseStream ) -> syn:: Result < Self > {
2324 let pool = input. parse ( ) ?;
2425 let mut version_table = None ;
26+ let mut verbose = false ;
2527
2628 while !input. is_empty ( ) {
2729 input. parse :: < Token ! [ , ] > ( ) ?;
@@ -34,6 +36,8 @@ impl Parse for MacroInput {
3436 input. parse :: < Token ! [ =] > ( ) ?;
3537 let value: syn:: LitStr = input. parse ( ) ?;
3638 version_table = Some ( value. value ( ) ) ;
39+ } else if key == "verbose" {
40+ verbose = true ;
3741 } else {
3842 return Err ( syn:: Error :: new (
3943 key. span ( ) ,
@@ -45,15 +49,24 @@ impl Parse for MacroInput {
4549 Ok ( MacroInput {
4650 pool,
4751 version_table,
52+ verbose,
4853 } )
4954 }
5055}
5156
52- /// Build a migration block for a single migration version.
53- /// Returns the generated code block and updates the baseline schema.
54- pub ( crate ) fn build_migration_block (
57+ /// Build a migration block with optional verbose logging.
58+ pub ( crate ) fn build_migration_block_verbose (
5559 migration : & vespertide_core:: MigrationPlan ,
5660 baseline_schema : & mut Vec < vespertide_core:: TableDef > ,
61+ verbose : bool ,
62+ ) -> Result < proc_macro2:: TokenStream , String > {
63+ build_migration_block_inner ( migration, baseline_schema, verbose)
64+ }
65+
66+ fn build_migration_block_inner (
67+ migration : & vespertide_core:: MigrationPlan ,
68+ baseline_schema : & mut Vec < vespertide_core:: TableDef > ,
69+ verbose : bool ,
5770) -> Result < proc_macro2:: TokenStream , String > {
5871 let version = migration. version ;
5972
@@ -70,63 +83,154 @@ pub(crate) fn build_migration_block(
7083 let _ = apply_action ( baseline_schema, action) ;
7184 }
7285
73- // Pre-generate SQL for all backends at compile time
74- // Each query may produce multiple SQL statements, so we flatten them
75- let mut pg_sqls = Vec :: new ( ) ;
76- let mut mysql_sqls = Vec :: new ( ) ;
77- let mut sqlite_sqls = Vec :: new ( ) ;
86+ // Generate version guard and SQL execution block
87+ let version_str = format ! ( "v{}" , version) ;
88+ let comment_str = migration. comment . as_deref ( ) . unwrap_or ( "" ) . to_string ( ) ;
89+
90+ let block = if verbose {
91+ // Verbose mode: preserve per-action grouping with action descriptions
92+ let total_sql_count: usize = queries
93+ . iter ( )
94+ . map ( |q| q. postgres . len ( ) . max ( q. mysql . len ( ) ) . max ( q. sqlite . len ( ) ) )
95+ . sum ( ) ;
96+ let total_sql_count_lit = total_sql_count;
97+
98+ let mut action_blocks = Vec :: new ( ) ;
99+ let mut global_idx: usize = 0 ;
100+
101+ for ( action_idx, q) in queries. iter ( ) . enumerate ( ) {
102+ let action_desc = format ! ( "{}" , q. action) ;
103+ let action_num = action_idx + 1 ;
104+ let total_actions = queries. len ( ) ;
105+
106+ let pg: Vec < String > = q
107+ . postgres
108+ . iter ( )
109+ . map ( |s| s. build ( DatabaseBackend :: Postgres ) )
110+ . collect ( ) ;
111+ let mysql: Vec < String > = q
112+ . mysql
113+ . iter ( )
114+ . map ( |s| s. build ( DatabaseBackend :: MySql ) )
115+ . collect ( ) ;
116+ let sqlite: Vec < String > = q
117+ . sqlite
118+ . iter ( )
119+ . map ( |s| s. build ( DatabaseBackend :: Sqlite ) )
120+ . collect ( ) ;
121+
122+ // Build per-SQL execution with global index
123+ let sql_count = pg. len ( ) . max ( mysql. len ( ) ) . max ( sqlite. len ( ) ) ;
124+ let mut sql_exec_blocks = Vec :: new ( ) ;
125+
126+ for i in 0 ..sql_count {
127+ let idx = global_idx + i + 1 ;
128+ let pg_sql = pg. get ( i) . cloned ( ) . unwrap_or_default ( ) ;
129+ let mysql_sql = mysql. get ( i) . cloned ( ) . unwrap_or_default ( ) ;
130+ let sqlite_sql = sqlite. get ( i) . cloned ( ) . unwrap_or_default ( ) ;
131+
132+ sql_exec_blocks. push ( quote ! {
133+ {
134+ let sql: & str = match backend {
135+ sea_orm:: DatabaseBackend :: Postgres => #pg_sql,
136+ sea_orm:: DatabaseBackend :: MySql => #mysql_sql,
137+ sea_orm:: DatabaseBackend :: Sqlite => #sqlite_sql,
138+ _ => #pg_sql,
139+ } ;
140+ if !sql. is_empty( ) {
141+ eprintln!( "[vespertide] [{}/{}] {}" , #idx, #total_sql_count_lit, sql) ;
142+ let stmt = sea_orm:: Statement :: from_string( backend, sql) ;
143+ __txn. execute_raw( stmt) . await . map_err( |e| {
144+ :: vespertide:: MigrationError :: DatabaseError ( format!( "Failed to execute SQL '{}': {}" , sql, e) )
145+ } ) ?;
146+ }
147+ }
148+ } ) ;
149+ }
150+ global_idx += sql_count;
78151
79- for q in & queries {
80- for stmt in & q. postgres {
81- pg_sqls. push ( stmt. build ( DatabaseBackend :: Postgres ) ) ;
152+ action_blocks. push ( quote ! {
153+ eprintln!( "[vespertide] Action {}/{}: {}" , #action_num, #total_actions, #action_desc) ;
154+ #( #sql_exec_blocks) *
155+ } ) ;
82156 }
83- for stmt in & q. mysql {
84- mysql_sqls. push ( stmt. build ( DatabaseBackend :: MySql ) ) ;
157+
158+ quote ! {
159+ if __version < #version {
160+ eprintln!( "[vespertide] Applying migration {} ({})" , #version_str, #comment_str) ;
161+ #( #action_blocks) *
162+
163+ let insert_sql = format!( "INSERT INTO {q}{}{q} (version) VALUES ({})" , __version_table, #version) ;
164+ let stmt = sea_orm:: Statement :: from_string( backend, insert_sql) ;
165+ __txn. execute_raw( stmt) . await . map_err( |e| {
166+ :: vespertide:: MigrationError :: DatabaseError ( format!( "Failed to insert version: {}" , e) )
167+ } ) ?;
168+
169+ eprintln!( "[vespertide] Migration {} applied successfully" , #version_str) ;
170+ }
85171 }
86- for stmt in & q. sqlite {
87- sqlite_sqls. push ( stmt. build ( DatabaseBackend :: Sqlite ) ) ;
172+ } else {
173+ // Non-verbose: flatten all SQL into one array (minimal overhead)
174+ let mut pg_sqls = Vec :: new ( ) ;
175+ let mut mysql_sqls = Vec :: new ( ) ;
176+ let mut sqlite_sqls = Vec :: new ( ) ;
177+
178+ for q in & queries {
179+ for stmt in & q. postgres {
180+ pg_sqls. push ( stmt. build ( DatabaseBackend :: Postgres ) ) ;
181+ }
182+ for stmt in & q. mysql {
183+ mysql_sqls. push ( stmt. build ( DatabaseBackend :: MySql ) ) ;
184+ }
185+ for stmt in & q. sqlite {
186+ sqlite_sqls. push ( stmt. build ( DatabaseBackend :: Sqlite ) ) ;
187+ }
88188 }
89- }
90189
91- // Generate version guard and SQL execution block
92- let block = quote ! {
93- if __version < #version {
94- // Select SQL statements based on backend
95- let sqls: & [ & str ] = match backend {
96- sea_orm:: DatabaseBackend :: Postgres => & [ #( #pg_sqls) , * ] ,
97- sea_orm:: DatabaseBackend :: MySql => & [ #( #mysql_sqls) , * ] ,
98- sea_orm:: DatabaseBackend :: Sqlite => & [ #( #sqlite_sqls) , * ] ,
99- _ => & [ #( #pg_sqls) , * ] , // Fallback to PostgreSQL syntax for unknown backends
100- } ;
101-
102- // Execute SQL statements
103- for sql in sqls {
104- if !sql. is_empty( ) {
105- let stmt = sea_orm:: Statement :: from_string( backend, * sql) ;
106- __txn. execute_raw( stmt) . await . map_err( |e| {
107- :: vespertide:: MigrationError :: DatabaseError ( format!( "Failed to execute SQL '{}': {}" , sql, e) )
108- } ) ?;
190+ quote ! {
191+ if __version < #version {
192+ let sqls: & [ & str ] = match backend {
193+ sea_orm:: DatabaseBackend :: Postgres => & [ #( #pg_sqls) , * ] ,
194+ sea_orm:: DatabaseBackend :: MySql => & [ #( #mysql_sqls) , * ] ,
195+ sea_orm:: DatabaseBackend :: Sqlite => & [ #( #sqlite_sqls) , * ] ,
196+ _ => & [ #( #pg_sqls) , * ] ,
197+ } ;
198+
199+ for sql in sqls {
200+ if !sql. is_empty( ) {
201+ let stmt = sea_orm:: Statement :: from_string( backend, * sql) ;
202+ __txn. execute_raw( stmt) . await . map_err( |e| {
203+ :: vespertide:: MigrationError :: DatabaseError ( format!( "Failed to execute SQL '{}': {}" , sql, e) )
204+ } ) ?;
205+ }
109206 }
110- }
111207
112- // Insert version record for this migration
113- let insert_sql = format! ( "INSERT INTO {q}{}{q} (version) VALUES ({})" , __version_table , #version ) ;
114- let stmt = sea_orm :: Statement :: from_string ( backend , insert_sql ) ;
115- __txn . execute_raw ( stmt ) . await . map_err ( |e| {
116- :: vespertide :: MigrationError :: DatabaseError ( format! ( "Failed to insert version: {}" , e ) )
117- } ) ? ;
208+ let insert_sql = format! ( "INSERT INTO {q}{}{q} ( version) VALUES ({})" , __version_table , #version ) ;
209+ let stmt = sea_orm :: Statement :: from_string ( backend , insert_sql ) ;
210+ __txn . execute_raw ( stmt ) . await . map_err ( |e| {
211+ :: vespertide :: MigrationError :: DatabaseError ( format! ( "Failed to insert version: {}" , e ) )
212+ } ) ? ;
213+ }
118214 }
119215 } ;
120216
121217 Ok ( block)
122218}
123219
124- /// Generate the final async migration block with all migrations.
125- pub ( crate ) fn generate_migration_code (
220+ fn generate_migration_code_inner (
126221 pool : & Expr ,
127222 version_table : & str ,
128223 migration_blocks : Vec < proc_macro2:: TokenStream > ,
224+ verbose : bool ,
129225) -> proc_macro2:: TokenStream {
226+ let verbose_current_version = if verbose {
227+ quote ! {
228+ eprintln!( "[vespertide] Current database version: {}" , __version) ;
229+ }
230+ } else {
231+ quote ! { }
232+ } ;
233+
130234 quote ! {
131235 async {
132236 use sea_orm:: { ConnectionTrait , TransactionTrait } ;
@@ -163,6 +267,8 @@ pub(crate) fn generate_migration_code(
163267 . and_then( |row| row. try_get:: <i32 >( "" , "version" ) . ok( ) )
164268 . unwrap_or( 0 ) as u32 ;
165269
270+ #verbose_current_version
271+
166272 // Execute each migration block within the same transaction
167273 #( #migration_blocks) *
168274
@@ -185,6 +291,7 @@ pub(crate) fn vespertide_migration_impl(
185291 Err ( e) => return e. to_compile_error ( ) ,
186292 } ;
187293 let pool = & input. pool ;
294+ let verbose = input. verbose ;
188295
189296 // Get project root from CARGO_MANIFEST_DIR (same as load_migrations_at_compile_time)
190297 let project_root = match env:: var ( "CARGO_MANIFEST_DIR" ) {
@@ -243,15 +350,15 @@ pub(crate) fn vespertide_migration_impl(
243350 for migration in & migrations {
244351 // Apply prefix to migration table names
245352 let prefixed_migration = migration. clone ( ) . with_prefix ( prefix) ;
246- match build_migration_block ( & prefixed_migration, & mut baseline_schema) {
353+ match build_migration_block_verbose ( & prefixed_migration, & mut baseline_schema, verbose ) {
247354 Ok ( block) => migration_blocks. push ( block) ,
248355 Err ( e) => {
249356 return syn:: Error :: new ( proc_macro2:: Span :: call_site ( ) , e) . to_compile_error ( ) ;
250357 }
251358 }
252359 }
253360
254- generate_migration_code ( pool, & version_table, migration_blocks)
361+ generate_migration_code_inner ( pool, & version_table, migration_blocks, verbose )
255362}
256363
257364/// Zero-runtime migration entry point.
0 commit comments