use std::process::Command;
use anstream::println;
use chrono::Utc;
use clap::Parser;
use eyre::eyre;
use owo_colors::OwoColorize;
use sha2::{Digest, Sha256};
use tracing::instrument;
use crate::{
App,
db::get_db_adapter,
error::Result,
migration::{
dir::get_migrations_dir,
planner::{MigrationState, Planner},
},
};
#[derive(Debug, Parser)]
pub struct Rollup {}
impl Rollup {
#[instrument(name = "rollup", skip_all)]
pub(crate) fn run(&self, opts: &App) -> Result {
let migrations_dir = get_migrations_dir(opts);
let local = migrations_dir.load()?;
let mut db = get_db_adapter(opts, false)?;
if Planner::new(opts)?
.status()?
.iter()
.any(|s| s.state != MigrationState::Applied)
{
return Err(eyre!(
"cannot rollup when there are pending, variant, or divergent migrations"
));
}
let url = &opts.options.url;
let up_sql = if url.starts_with("postgres://") || url.starts_with("postgresql://") {
let out = Command::new("pg_dump")
.arg("--schema-only")
.arg("--no-owner")
.arg("--no-privileges")
.arg("--exclude-schema=crude")
.arg(format!("--dbname={url}"))
.output()?;
String::from_utf8_lossy(&out.stdout).into_owned()
} else {
let out = Command::new("sqlite3").arg(url).arg(".schema").output()?;
String::from_utf8_lossy(&out.stdout)
.lines()
.filter(|l| !l.contains("crude_migrations"))
.collect::<Vec<_>>()
.join("\n")
};
let ts = Utc::now();
let compound_name = format!("{}_rollup", ts.format("%Y%m%d%H%M%S"));
let mut hasher = Sha256::new();
hasher.update(up_sql.as_bytes());
let hash = hex::encode(hasher.finalize());
let seed_sql = if url.starts_with("postgres://") || url.starts_with("postgresql://") {
let out = Command::new("pg_dump")
.arg("--data-only")
.arg("--inserts")
.arg("--no-owner")
.arg("--no-privileges")
.arg("--exclude-schema=crude")
.arg(format!("--dbname={url}"))
.output()?;
String::from_utf8_lossy(&out.stdout).into_owned()
} else {
let out = Command::new("sqlite3").arg(url).arg(".dump").output()?;
String::from_utf8_lossy(&out.stdout)
.lines()
.filter(|l| !l.contains("crude_migrations"))
.collect::<Vec<_>>()
.join("\n")
};
migrations_dir.create_migration(&compound_name, Some(&up_sql), Some(&seed_sql))?;
db.clear_migrations()?;
db.record_baseline(&compound_name, &hash)?;
local
.iter()
.filter(|m| m.name != "init")
.try_for_each(|m| {
println!("{} {}", "Rolled up".cyan(), m.compound_name);
migrations_dir.remove_migration(&m.compound_name)
})?;
Ok(())
}
}