use chrono::{DateTime, NaiveDateTime, Utc};
use mysql_async::prelude::*;
use mysql_async::Pool;
use crate::dialect::{mysql::MysqlDialect, DatabaseDialect};
use crate::error::{Result, WaypointError};
use crate::history::AppliedMigration;
fn fq(schema: &str, table: &str) -> String {
MysqlDialect.qualified_table(schema, table)
}
pub async fn history_table_exists(pool: &Pool, schema: &str, table: &str) -> Result<bool> {
let mut conn = pool.get_conn().await?;
let exists: Option<i64> = conn
.exec_first(
"SELECT 1 FROM information_schema.tables \
WHERE table_schema = ? AND table_name = ? LIMIT 1",
(schema, table),
)
.await?;
Ok(exists.is_some())
}
pub async fn get_applied_migrations(
pool: &Pool,
schema: &str,
table: &str,
) -> Result<Vec<AppliedMigration>> {
let sql = format!(
"SELECT installed_rank, version, description, type, script, checksum, \
installed_by, installed_on, execution_time, success, reversal_sql \
FROM {} ORDER BY installed_rank",
fq(schema, table)
);
let mut conn = pool.get_conn().await?;
let rows: Vec<mysql_async::Row> = conn.query(&sql).await?;
let mut out = Vec::with_capacity(rows.len());
for mut row in rows {
let installed_rank: i32 = row
.take("installed_rank")
.ok_or_else(|| WaypointError::ConfigError("missing installed_rank".into()))?;
let version: Option<String> = row.take("version").unwrap_or(None);
let description: String = row
.take("description")
.ok_or_else(|| WaypointError::ConfigError("missing description".into()))?;
let migration_type: String = row
.take("type")
.ok_or_else(|| WaypointError::ConfigError("missing type".into()))?;
let script: String = row
.take("script")
.ok_or_else(|| WaypointError::ConfigError("missing script".into()))?;
let checksum: Option<i32> = row.take("checksum").unwrap_or(None);
let installed_by: String = row
.take("installed_by")
.ok_or_else(|| WaypointError::ConfigError("missing installed_by".into()))?;
let installed_on_raw: NaiveDateTime = row
.take("installed_on")
.ok_or_else(|| WaypointError::ConfigError("missing installed_on".into()))?;
let installed_on = DateTime::<Utc>::from_naive_utc_and_offset(installed_on_raw, Utc);
let execution_time: i32 = row
.take("execution_time")
.ok_or_else(|| WaypointError::ConfigError("missing execution_time".into()))?;
let success_raw: i8 = row
.take("success")
.ok_or_else(|| WaypointError::ConfigError("missing success".into()))?;
let success = success_raw != 0;
let reversal_sql: Option<String> = row.take("reversal_sql").unwrap_or(None);
out.push(AppliedMigration {
installed_rank,
version,
description,
migration_type,
script,
checksum,
installed_by,
installed_on,
execution_time,
success,
reversal_sql,
});
}
Ok(out)
}
#[allow(clippy::too_many_arguments)]
pub async fn insert_applied_migration(
pool: &Pool,
schema: &str,
table: &str,
version: Option<&str>,
description: &str,
migration_type: &str,
script: &str,
checksum: Option<i32>,
installed_by: &str,
execution_time: i32,
success: bool,
) -> Result<()> {
let fq = fq(schema, table);
let sql_max = format!("SELECT COALESCE(MAX(installed_rank), 0) + 1 FROM {}", fq);
let mut conn = pool.get_conn().await?;
let next_rank: i32 = conn.query_first(&sql_max).await?.unwrap_or(1);
let insert_sql = format!(
"INSERT INTO {} \
(installed_rank, version, description, type, script, checksum, \
installed_by, execution_time, success) \
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
fq
);
conn.exec_drop(
&insert_sql,
(
next_rank,
version,
description,
migration_type,
script,
checksum,
installed_by,
execution_time,
success as i8,
),
)
.await?;
Ok(())
}
pub async fn has_entries(pool: &Pool, schema: &str, table: &str) -> Result<bool> {
let sql = format!("SELECT 1 FROM {} LIMIT 1", fq(schema, table));
let mut conn = pool.get_conn().await?;
let row: Option<i64> = conn.query_first(&sql).await?;
Ok(row.is_some())
}
pub async fn delete_failed_migrations(pool: &Pool, schema: &str, table: &str) -> Result<u64> {
let sql = format!("DELETE FROM {} WHERE success = 0", fq(schema, table));
let mut conn = pool.get_conn().await?;
conn.query_drop(&sql).await?;
Ok(conn.affected_rows())
}
pub async fn update_checksum(
pool: &Pool,
schema: &str,
table: &str,
version: &str,
new_checksum: i32,
) -> Result<()> {
let sql = format!(
"UPDATE {} SET checksum = ? WHERE version = ?",
fq(schema, table)
);
let mut conn = pool.get_conn().await?;
conn.exec_drop(&sql, (new_checksum, version)).await?;
Ok(())
}
pub async fn update_repeatable_checksum(
pool: &Pool,
schema: &str,
table: &str,
script: &str,
new_checksum: i32,
) -> Result<()> {
let sql = format!(
"UPDATE {} SET checksum = ? WHERE script = ? AND version IS NULL",
fq(schema, table)
);
let mut conn = pool.get_conn().await?;
conn.exec_drop(&sql, (new_checksum, script)).await?;
Ok(())
}