use crate::{PgBackend, classify};
use persistant_relational::{MigrateError, MigrateReport, Migrates, Migration, RelError};
use sqlx::SqlSafeStr;
use sqlx::migrate::{Migration as SqlxMigration, MigrationType, Migrator};
pub(crate) fn migrator(migrations: &[Migration]) -> Migrator {
let sqlx_migrations: Vec<SqlxMigration> = migrations
.iter()
.map(|m| {
SqlxMigration::new(
m.version,
m.description.clone().into(),
MigrationType::Simple,
sqlx::AssertSqlSafe(m.sql.clone()).into_sql_str(),
false,
)
})
.collect();
Migrator::with_migrations(sqlx_migrations)
}
pub(crate) fn map_error(
e: sqlx::migrate::MigrateError,
classify: fn(sqlx::Error) -> RelError,
) -> MigrateError {
match e {
sqlx::migrate::MigrateError::VersionMismatch(version) => {
MigrateError::ChecksumMismatch { version }
}
sqlx::migrate::MigrateError::VersionMissing(version) => {
MigrateError::NewerThanProgram { version }
}
sqlx::migrate::MigrateError::Dirty(version) => MigrateError::Dirty { version },
sqlx::migrate::MigrateError::ExecuteMigration(e, version) => MigrateError::Failed {
version,
error: classify(e),
},
other => MigrateError::Backend(other.to_string()),
}
}
pub(crate) fn backend_error(e: sqlx::Error) -> MigrateError {
MigrateError::Backend(e.to_string())
}
macro_rules! run_on_own_connection {
($pool:expr, $migrations:expr, $classify:expr) => {{
let mut conn = $pool
.acquire()
.await
.map_err(crate::migrate::backend_error)?;
match crate::migrate::migrator($migrations)
.run_direct(None, &mut *conn, false)
.await
{
Ok(()) => Ok(()),
Err(e) => {
let _ = sqlx::Connection::close(conn.detach()).await;
Err(crate::migrate::map_error(e, $classify))
}
}
}};
}
#[cfg(any(feature = "mysql", feature = "sqlite"))]
pub(crate) use run_on_own_connection;
impl Migrates for PgBackend {
async fn migrate(&self, migrations: &[Migration]) -> Result<MigrateReport, MigrateError> {
let before = Migrates::applied_versions(self).await?;
run_on_own_connection!(self.pool, migrations, classify)?;
let after = Migrates::applied_versions(self).await?;
let applied = after
.iter()
.copied()
.filter(|v| !before.contains(v))
.collect();
Ok(MigrateReport {
applied,
already_applied: before,
})
}
async fn applied_versions(&self) -> Result<Vec<i64>, MigrateError> {
let exists: bool = sqlx::query_scalar("SELECT to_regclass('_sqlx_migrations') IS NOT NULL")
.fetch_one(&self.pool)
.await
.map_err(backend_error)?;
if !exists {
return Ok(Vec::new());
}
sqlx::query_scalar("SELECT version FROM _sqlx_migrations WHERE success ORDER BY version")
.fetch_all(&self.pool)
.await
.map_err(backend_error)
}
}
impl PgBackend {
pub async fn migrate(&self, migrations: &[Migration]) -> Result<MigrateReport, MigrateError> {
Migrates::migrate(self, migrations).await
}
pub async fn applied_versions(&self) -> Result<Vec<i64>, MigrateError> {
Migrates::applied_versions(self).await
}
}