Skip to content

Commit 82ba69e

Browse files
committed
Add verbose option
1 parent dc3ce13 commit 82ba69e

2 files changed

Lines changed: 154 additions & 47 deletions

File tree

crates/vespertide-macro/src/lib.rs

Lines changed: 153 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,19 @@ use vespertide_loader::{
1111
load_config_or_default, load_migrations_at_compile_time, load_models_at_compile_time,
1212
};
1313
use vespertide_planner::apply_action;
14-
use vespertide_query::{DatabaseBackend, build_plan_queries};
14+
use vespertide_query::{build_plan_queries, DatabaseBackend};
1515

1616
struct MacroInput {
1717
pool: Expr,
1818
version_table: Option<String>,
19+
verbose: bool,
1920
}
2021

2122
impl 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.

examples/app/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ async fn main() -> Result<()> {
2222

2323
println!("Successfully connected to SQLite database!");
2424

25-
vespertide::vespertide_migration!(db, version_table = "vespertide_version").await?;
25+
vespertide::vespertide_migration!(db, version_table = "vespertide_version", verbose).await?;
2626

2727
Ok(())
2828
}

0 commit comments

Comments
 (0)