#[cfg(feature = "sea-orm")]
pub mod sea;
use std::path::{Path, PathBuf};
use purwa_core::{AppConfig, PurwaConfigError};
use sqlx::Executor;
use sqlx::PgPool;
use sqlx::migrate::{Migrate, MigrateError, Migrator};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum PurwaOrmError {
#[error(transparent)]
Sqlx(#[from] sqlx::Error),
#[error(transparent)]
Migrate(#[from] MigrateError),
#[error(transparent)]
Config(#[from] PurwaConfigError),
#[error(
"database URL is not set (purwa.toml [database].url, PURWA_DATABASE__URL, or DATABASE_URL)"
)]
DatabaseUrlMissing,
}
pub fn default_migrations_dir() -> PathBuf {
PathBuf::from("database/migrations")
}
pub async fn connect_pool(database_url: &str) -> Result<PgPool, sqlx::Error> {
sqlx::postgres::PgPoolOptions::new()
.max_connections(5)
.connect(database_url)
.await
}
pub fn database_url_from_config(cfg: &AppConfig) -> Result<String, PurwaOrmError> {
cfg.database_url().ok_or(PurwaOrmError::DatabaseUrlMissing)
}
pub async fn migrate_up(pool: &PgPool, dir: &Path) -> Result<(), PurwaOrmError> {
let m = Migrator::new(dir).await?;
m.run(pool).await?;
Ok(())
}
pub async fn migrate_undo(pool: &PgPool, dir: &Path, target: i64) -> Result<(), PurwaOrmError> {
let m = Migrator::new(dir).await?;
m.undo(pool, target).await?;
Ok(())
}
pub async fn migrate_rollback_one(pool: &PgPool, dir: &Path) -> Result<(), PurwaOrmError> {
let Some(target) = rollback_target(pool).await? else {
return Ok(());
};
migrate_undo(pool, dir, target).await
}
async fn rollback_target(pool: &PgPool) -> Result<Option<i64>, PurwaOrmError> {
let mut conn = pool.acquire().await?;
conn.ensure_migrations_table().await?;
if let Some(v) = conn.dirty_version().await? {
return Err(MigrateError::Dirty(v).into());
}
let applied = conn.list_applied_migrations().await?;
if applied.is_empty() {
return Ok(None);
}
let mut versions: Vec<i64> = applied.into_iter().map(|a| a.version).collect();
versions.sort();
let target = if versions.len() <= 1 {
0_i64
} else {
versions[versions.len() - 2]
};
Ok(Some(target))
}
pub async fn migrate_fresh(pool: &PgPool, dir: &Path) -> Result<(), PurwaOrmError> {
pool.execute("DROP SCHEMA IF EXISTS public CASCADE")
.await
.map_err(PurwaOrmError::Sqlx)?;
pool.execute("CREATE SCHEMA public")
.await
.map_err(PurwaOrmError::Sqlx)?;
pool.execute("GRANT ALL ON SCHEMA public TO PUBLIC")
.await
.map_err(PurwaOrmError::Sqlx)?;
migrate_up(pool, dir).await
}