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, $exists:expr) => {{
let mut conn = $pool
.acquire()
.await
.map_err(crate::migrate::backend_error)?;
let mut failure = None;
let mut before = Vec::new();
let mut after = Vec::new();
'run: {
if let Err(e) = sqlx::migrate::Migrate::lock(&mut *conn).await {
failure = Some(persistant_relational::MigrateError::Backend(e.to_string()));
break 'run;
}
match crate::migrate::ledger(&mut *conn, $exists).await {
Ok(v) => before = v,
Err(e) => {
failure = Some(e);
break 'run;
}
}
if let Err(e) = crate::migrate::migrator($migrations)
.run_direct(None, &mut *conn, false)
.await
{
failure = Some(crate::migrate::map_error(e, $classify));
break 'run;
}
match crate::migrate::ledger(&mut *conn, $exists).await {
Ok(v) => after = v,
Err(e) => {
failure = Some(e);
break 'run;
}
}
if let Err(e) = sqlx::migrate::Migrate::unlock(&mut *conn).await {
failure = Some(persistant_relational::MigrateError::Backend(e.to_string()));
}
}
match failure {
None => {
let applied = after
.iter()
.copied()
.filter(|v| !before.contains(v))
.collect();
Ok(persistant_relational::MigrateReport {
applied,
already_applied: before,
})
}
Some(e) => {
let _ = sqlx::Connection::close(conn.detach()).await;
Err(e)
}
}
}};
}
#[cfg(any(feature = "mysql", feature = "sqlite"))]
pub(crate) use run_on_own_connection;
pub(crate) async fn ledger<C>(conn: &mut C, exists: &str) -> Result<Vec<i64>, MigrateError>
where
C: sqlx::Connection,
for<'c> &'c mut C: sqlx::Executor<'c, Database = C::Database>,
for<'q> i64: sqlx::Decode<'q, C::Database> + sqlx::Type<C::Database>,
<C::Database as sqlx::Database>::Arguments: sqlx::IntoArguments<C::Database>,
usize: sqlx::ColumnIndex<<C::Database as sqlx::Database>::Row>,
{
let there: i64 = sqlx::query_scalar(sqlx::AssertSqlSafe(exists.to_string()))
.fetch_one(&mut *conn)
.await
.map_err(backend_error)?;
if there == 0 {
return Ok(Vec::new());
}
sqlx::query_scalar(sqlx::AssertSqlSafe(
"SELECT version FROM _sqlx_migrations WHERE success ORDER BY version".to_string(),
))
.fetch_all(&mut *conn)
.await
.map_err(backend_error)
}
impl Migrates for PgBackend {
async fn migrate(&self, migrations: &[Migration]) -> Result<MigrateReport, MigrateError> {
run_on_own_connection!(
self.pool,
migrations,
classify,
"SELECT CASE WHEN to_regclass('_sqlx_migrations') IS NULL THEN 0::int8 ELSE 1::int8 END"
)
}
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
}
}