use crate::colors::*;
use anyhow::Result;
use qail_core::migrate::{diff_schemas_checked, parse_qail};
use qail_pg::PgDriver;
use crate::migrations::{
MigrationReceipt, acquire_migration_lock, ensure_migration_table, now_epoch_ms, runtime_actor,
runtime_git_sha, write_migration_receipt,
};
use crate::util::parse_pg_url;
pub async fn migrate_reset(
schema_file: &str,
url: &str,
wait_for_lock: bool,
lock_timeout_secs: Option<u64>,
) -> Result<()> {
println!("{} {}", "🔄 Resetting database:".cyan().bold(), url);
println!();
let target_content = qail_core::schema_source::read_qail_schema_source(schema_file)
.map_err(|e| anyhow::anyhow!("Cannot read {}: {}", schema_file, e))?;
let target_schema = parse_qail(&target_content)
.map_err(|e| anyhow::anyhow!("Failed to parse {}: {}", schema_file, e))?;
let empty_schema = Default::default();
let create_cmds = diff_schemas_checked(&empty_schema, &target_schema).map_err(|e| {
anyhow::anyhow!(
"State-based diff unsupported for target reset schema '{}': {}",
schema_file,
e
)
})?;
let (host, port, user, password, database) = parse_pg_url(url)?;
let mut driver = if let Some(pwd) = password {
PgDriver::connect_with_password(&host, port, &user, &database, &pwd)
.await
.map_err(|e| anyhow::anyhow!("Failed to connect: {}", e))?
} else {
PgDriver::connect(&host, port, &user, &database)
.await
.map_err(|e| anyhow::anyhow!("Failed to connect: {}", e))?
};
acquire_migration_lock(
&mut driver,
"migrate reset",
wait_for_lock,
lock_timeout_secs,
Some(database.as_str()),
)
.await?;
let live_schema = crate::shadow::introspect_schema(&mut driver)
.await
.map_err(|e| anyhow::anyhow!("Failed to introspect live schema: {}", e))?;
let drop_cmds = diff_schemas_checked(&live_schema, &empty_schema).map_err(|e| {
anyhow::anyhow!(
"State-based diff unsupported for live reset schema '{}': {}",
schema_file,
e
)
})?;
if drop_cmds.is_empty() {
println!(" {} No objects to drop", "○".dimmed());
} else {
println!(" {} Dropping {} object(s)...", "↓".red(), drop_cmds.len());
driver
.begin()
.await
.map_err(|e| anyhow::anyhow!("Failed to begin transaction: {}", e))?;
for (i, cmd) in drop_cmds.iter().enumerate() {
print!(" [{}] {} ", i + 1, format!("{:?}", cmd.action).red());
match driver.execute(cmd).await {
Ok(_) => println!("{}", "✓".green()),
Err(e) => {
println!("{}", "✗".red());
let _ = driver.rollback().await;
anyhow::bail!("Drop failed at step {}: {}", i + 1, e);
}
}
}
driver
.commit()
.await
.map_err(|e| anyhow::anyhow!("Failed to commit drops: {}", e))?;
}
println!(" {} Clearing migration history...", "⊘".yellow());
let clear_cmd = qail_core::prelude::Qail::del("_qail_migrations");
match driver.execute(&clear_cmd).await {
Ok(_) => println!(" {} Cleared migration history", "✓".green()),
Err(e) => {
let msg = e.to_string();
if msg.contains("does not exist") || msg.contains("42P01") {
println!(" {} No migration history to clear", "○".dimmed());
} else {
anyhow::bail!(
"Failed to clear migration history (stale rows may cause drift): {}",
e
);
}
}
}
if create_cmds.is_empty() {
println!(" {} No objects to create", "○".dimmed());
} else {
println!(
"\n {} Creating {} object(s)...",
"↑".green(),
create_cmds.len()
);
let started_ms = now_epoch_ms();
driver
.begin()
.await
.map_err(|e| anyhow::anyhow!("Failed to begin transaction: {}", e))?;
for (i, cmd) in create_cmds.iter().enumerate() {
print!(" [{}] {} ", i + 1, format!("{:?}", cmd.action).green());
match driver.execute(cmd).await {
Ok(_) => println!("{}", "✓".green()),
Err(e) => {
println!("{}", "✗".red());
let _ = driver.rollback().await;
anyhow::bail!("Create failed at step {}: {}", i + 1, e);
}
}
}
ensure_migration_table(&mut driver)
.await
.map_err(|e| anyhow::anyhow!("Failed to bootstrap migration table: {}", e))?;
let version = crate::time::timestamp_version();
let name = format!("reset_{}", version);
let checksum = crate::time::md5_hex(&target_content);
let finished_ms = now_epoch_ms();
let receipt = MigrationReceipt {
version: version.clone(),
name: name.clone(),
checksum,
sql_up: "-- reset migration".to_string(),
git_sha: runtime_git_sha(),
qail_version: env!("CARGO_PKG_VERSION").to_string(),
actor: runtime_actor(),
started_at_ms: Some(started_ms),
finished_at_ms: Some(finished_ms),
duration_ms: Some(finished_ms.saturating_sub(started_ms)),
affected_rows_est: None,
risk_summary: Some(format!(
"source=reset;drop_cmds={};create_cmds={}",
drop_cmds.len(),
create_cmds.len()
)),
shadow_checksum: None,
};
write_migration_receipt(&mut driver, &receipt)
.await
.map_err(|e| anyhow::anyhow!("Failed to record migration: {}", e))?;
driver
.commit()
.await
.map_err(|e| anyhow::anyhow!("Failed to commit creates: {}", e))?;
println!(
"\n {} Recorded as migration: {}",
"✓".green(),
version.white()
);
}
println!(
"\n{} Database reset to {} successfully",
"✅".green(),
schema_file.cyan()
);
Ok(())
}