use std::{fs::write, process::Command};
use ::postgres::{Client, NoTls};
use eyre::eyre;
use rusqlite::Connection;
use tracing::debug;
use crate::{App, error::Result, migration::Migration};
mod postgres;
mod sqlite;
pub use postgres::PostgresAdapter;
pub use sqlite::SqliteAdapter;
pub trait DatabaseAdapter {
fn init_up_sql(&self) -> &'static str;
fn load_migrations(&mut self) -> Result<Vec<Migration>>;
fn run_up_migration(&mut self, migration: &Migration) -> Result<()>;
fn run_down_migration(&mut self, migration: &Migration) -> Result<()>;
fn update_migration_hash(&mut self, name: &str, hash: &str) -> Result<()>;
fn clear_migrations(&mut self) -> Result<()>;
fn record_baseline(&mut self, name: &str, hash: &str) -> Result<()>;
}
pub fn get_db_adapter(opts: &App) -> Result<Box<dyn DatabaseAdapter>> {
let url = &opts.options.url;
if url.starts_with("postgres://") || url.starts_with("postgresql://") {
let client = Client::connect(url, NoTls)?;
Ok(Box::new(PostgresAdapter::new(client)))
} else if url.starts_with("sqlite://") {
let conn = Connection::open(url)?;
Ok(Box::new(SqliteAdapter::new(conn)))
} else {
Err(eyre!("unsupported database URL: {}", url))
}
}
pub fn maybe_dump_schema(opts: &App) -> Result<()> {
if let Some(path) = &opts.options.schema {
let url = &opts.options.url;
if url.starts_with("postgres://") || url.starts_with("postgresql://") {
let output = Command::new("pg_dump")
.arg("--schema-only")
.arg("--no-owner")
.arg("--no-privileges")
.arg(format!("--dbname={url}"))
.output()?;
write(path, &output.stdout)?;
} else if url.starts_with("sqlite://") {
let output = Command::new("sqlite3").arg(url).arg(".schema").output()?;
write(path, &output.stdout)?;
}
debug!("schema dumped to {path}");
}
Ok(())
}