use std::env;
use arcature_db::{Db, DbConfig};
use crate::error::{CommandError, DbCommandError};
use crate::process::{ProcessSpec, run};
use crate::project::discover;
const ADVISORY_LOCK_KEY: i64 = 0x4152_4354;
pub(crate) fn run_migrate() -> Result<(), CommandError> {
run_with_lock(&["up"])
}
pub(crate) fn run_rollback(steps: Option<u32>) -> Result<(), CommandError> {
let args: Vec<String> = match steps {
Some(n) => vec!["down".into(), "--num".into(), n.to_string()],
None => vec!["down".into()],
};
run_with_lock_owned(&args)
}
pub(crate) fn run_fresh() -> Result<(), CommandError> {
run_with_lock(&["fresh"])
}
pub(crate) fn run_reset() -> Result<(), CommandError> {
run_with_lock(&["reset"])
}
pub(crate) fn run_refresh() -> Result<(), CommandError> {
run_with_lock(&["refresh"])
}
fn run_with_lock(args: &[&str]) -> Result<(), CommandError> {
let owned: Vec<String> = args.iter().map(|s| (*s).to_string()).collect();
run_with_lock_owned(&owned)
}
fn run_with_lock_owned(args: &[String]) -> Result<(), CommandError> {
let database_url = env::var("DATABASE_URL").map_err(|_| DbCommandError::MissingDatabaseUrl)?;
let project = discover()?;
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.map_err(|e| DbCommandError::Runtime(e.to_string()))?;
let (db, mut connection) = runtime.block_on(async {
let db = Db::connect(DbConfig::new(&database_url)?.application_name("arcature-db-migrate"))
.await?;
let mut connection = db
.sqlx()
.acquire()
.await
.map_err(DbCommandError::AdvisoryLock)?;
arcature_db::sqlx::query("SELECT pg_advisory_lock($1)")
.bind(ADVISORY_LOCK_KEY)
.execute(&mut *connection)
.await
.map_err(DbCommandError::AdvisoryLock)?;
Ok::<_, DbCommandError>((db, connection))
})?;
let mut spec_args: Vec<String> =
vec!["run".into(), "-p".into(), "migration".into(), "--".into()];
spec_args.extend(args.iter().cloned());
let spec = ProcessSpec::new("cargo", project.root().to_path_buf());
let spec = spec_args
.iter()
.fold(spec, |spec, arg| spec.arg(arg.clone()));
let result = run(&spec);
let cleanup = runtime.block_on(async {
let unlock = arcature_db::sqlx::query_scalar::<_, bool>("SELECT pg_advisory_unlock($1)")
.bind(ADVISORY_LOCK_KEY)
.fetch_one(&mut *connection)
.await
.map_err(DbCommandError::AdvisoryLock)
.and_then(|released| {
released
.then_some(())
.ok_or(DbCommandError::AdvisoryLockNotOwned)
});
let close = connection
.close()
.await
.map_err(DbCommandError::AdvisoryLock);
db.close().await;
unlock?;
close
});
cleanup?;
result?;
Ok(())
}