mod cli;
use std::fs;
use std::io;
use std::path::PathBuf;
use std::process::{exit, ExitStatus};
use clap::Parser;
use color_eyre::eyre::{bail, Result, WrapErr};
use color_eyre::{Help, SectionExt};
use postgresfixture::{
cluster, coordinate, lock,
runtime::{self, strategy::RuntimeStrategy},
};
fn main() -> Result<()> {
color_eyre::install()?;
let cli = cli::Cli::parse();
let result = match cli.command {
cli::Commands::Shell { cluster, database, lifecycle } => run(
cluster.dir,
&database.name,
lifecycle.destroy,
initialise(cluster.mode),
|cluster| {
check_exit(
cluster
.shell(&database.name)
.wrap_err("Starting PostgreSQL shell in cluster failed")?,
)
},
),
cli::Commands::Exec { cluster, database, command, args, lifecycle } => run(
cluster.dir,
&database.name,
lifecycle.destroy,
initialise(cluster.mode),
|cluster| {
check_exit(
cluster
.exec(&database.name, command, &args)
.wrap_err("Executing command in cluster failed")?,
)
},
),
cli::Commands::Runtimes => {
let strategy = runtime::strategy::default();
let mut runtimes: Vec<_> = strategy.runtimes().collect();
let default = strategy.fallback();
runtimes.sort_by(|ra, rb| ra.version.cmp(&rb.version));
for runtime in runtimes {
let default = match default {
Some(ref default) if default == &runtime => "=>",
_ => "",
};
println!(
"{default:2} {version:10} {bindir}",
bindir = runtime.bindir.display(),
version = runtime.version,
)
}
Ok(0)
}
};
match result {
Ok(code) => exit(code),
Err(report) => Err(report),
}
}
fn check_exit(status: ExitStatus) -> Result<i32> {
match status.code() {
Some(code) => Ok(code),
None => bail!("Command terminated: {status}"),
}
}
const UUID_NS: uuid::Uuid = uuid::Uuid::from_u128(93875103436633470414348750305797058811);
fn run<INIT, ACTION>(
database_dir: PathBuf,
database_name: &str,
destroy: bool,
initialise: INIT,
action: ACTION,
) -> Result<i32>
where
INIT: std::panic::UnwindSafe + FnOnce(&cluster::Cluster) -> Result<(), cluster::ClusterError>,
ACTION: FnOnce(&cluster::Cluster) -> Result<i32> + std::panic::UnwindSafe,
{
match fs::create_dir(&database_dir) {
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => (),
err @ Err(_) => err
.wrap_err("Could not create database directory")
.with_section(|| format!("{}", database_dir.display()).header("Database directory:"))?,
_ => (),
};
let database_dir = database_dir
.canonicalize()
.wrap_err("Could not canonicalize database directory")
.with_section(|| format!("{}", database_dir.display()).header("Database directory:"))?;
let lock_uuid = uuid::Uuid::new_v5(&UUID_NS, format!("{:?}", &database_dir).as_bytes());
let lock = lock::UnlockedFile::try_from(&lock_uuid)
.wrap_err("Could not create UUID-based lock file")
.with_section(|| lock_uuid.to_string().header("UUID for lock file:"))?;
let strategy = runtime::strategy::default();
let cluster = cluster::Cluster::new(&database_dir, strategy)?;
let runner = if destroy {
coordinate::run_and_destroy
} else {
coordinate::run_and_stop
};
runner(&cluster, lock, |cluster: &cluster::Cluster| {
initialise(cluster)?;
if !cluster
.databases()
.wrap_err("Could not list databases")?
.contains(&database_name.to_string())
{
cluster
.createdb(database_name)
.wrap_err("Could not create database")
.with_section(|| database_name.to_owned().header("Database:"))?;
}
ctrlc::set_handler(|| ()).wrap_err("Could not set signal handler")?;
action(cluster)
})?
}
fn initialise(
mode: Option<cli::Mode>,
) -> impl std::panic::UnwindSafe + FnOnce(&cluster::Cluster) -> Result<(), cluster::ClusterError> {
match mode {
Some(cli::Mode::Fast) => {
|cluster: &cluster::Cluster| {
let mut conn = cluster.connect("template1")?;
conn.execute("ALTER SYSTEM SET fsync = 'off'", &[])?;
conn.execute("ALTER SYSTEM SET full_page_writes = 'off'", &[])?;
conn.execute("ALTER SYSTEM SET synchronous_commit = 'off'", &[])?;
conn.execute("SELECT pg_reload_conf()", &[])?;
Ok(())
}
}
Some(cli::Mode::Slow) => {
|cluster: &cluster::Cluster| {
let mut conn = cluster.connect("template1")?;
conn.execute("ALTER SYSTEM RESET fsync", &[])?;
conn.execute("ALTER SYSTEM RESET full_page_writes", &[])?;
conn.execute("ALTER SYSTEM RESET synchronous_commit", &[])?;
conn.execute("SELECT pg_reload_conf()", &[])?;
Ok(())
}
}
None => |_: &cluster::Cluster| Ok(()),
}
}