use chrono::{DateTime, Utc};
use crate::db::DbClient;
use crate::error::{Result, WaypointError};
#[derive(Debug, Clone)]
pub struct AppliedMigration {
pub installed_rank: i32,
pub version: Option<String>,
pub description: String,
pub migration_type: String,
pub script: String,
pub checksum: Option<i32>,
pub installed_by: String,
pub installed_on: DateTime<Utc>,
pub execution_time: i32,
pub success: bool,
pub reversal_sql: Option<String>,
}
#[cfg(feature = "postgres")]
pub use crate::engines::postgres::history::{
create_history_table, delete_failed_migrations, get_applied_migrations, has_entries,
history_table_exists, insert_applied_migration, next_installed_rank, update_checksum,
update_repeatable_checksum,
};
pub async fn create_history_table_db(client: &DbClient, schema: &str, table: &str) -> Result<()> {
let dialect = client.dialect();
let ddl = dialect.history_table_ddl(schema, table);
if let Err(e) = client.execute_raw(&ddl).await {
if !is_benign_index_dup(&e) {
return Err(e);
}
}
upgrade_history_table_db(client, schema, table).await?;
Ok(())
}
async fn upgrade_history_table_db(client: &DbClient, schema: &str, table: &str) -> Result<()> {
let dialect = client.dialect();
let fq = dialect.qualified_table(schema, table);
let sql = match client.dialect_kind() {
crate::dialect::DialectKind::Postgres => {
format!("ALTER TABLE {fq} ADD COLUMN IF NOT EXISTS reversal_sql TEXT")
}
crate::dialect::DialectKind::Mysql => {
format!("ALTER TABLE {fq} ADD COLUMN IF NOT EXISTS reversal_sql LONGTEXT")
}
};
if let Err(e) = client.execute_raw(&sql).await {
log::debug!("History table upgrade (reversal_sql): {}", e);
}
Ok(())
}
fn is_benign_index_dup(e: &WaypointError) -> bool {
let msg = e.to_string().to_lowercase();
msg.contains("er_dup_keyname") || msg.contains("duplicate key name")
}
pub async fn history_table_exists_db(client: &DbClient, schema: &str, table: &str) -> Result<bool> {
match client {
#[cfg(feature = "postgres")]
DbClient::Postgres(c) => {
crate::engines::postgres::history::history_table_exists(c, schema, table).await
}
#[cfg(feature = "mysql")]
DbClient::Mysql(pool) => {
crate::engines::mysql::history::history_table_exists(pool, schema, table).await
}
}
}
pub async fn get_applied_migrations_db(
client: &DbClient,
schema: &str,
table: &str,
) -> Result<Vec<AppliedMigration>> {
match client {
#[cfg(feature = "postgres")]
DbClient::Postgres(c) => {
crate::engines::postgres::history::get_applied_migrations(c, schema, table).await
}
#[cfg(feature = "mysql")]
DbClient::Mysql(pool) => {
crate::engines::mysql::history::get_applied_migrations(pool, schema, table).await
}
}
}
#[allow(clippy::too_many_arguments)]
pub async fn insert_applied_migration_db(
client: &DbClient,
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<()> {
match client {
#[cfg(feature = "postgres")]
DbClient::Postgres(c) => {
crate::engines::postgres::history::insert_applied_migration(
c,
schema,
table,
version,
description,
migration_type,
script,
checksum,
installed_by,
execution_time,
success,
)
.await
}
#[cfg(feature = "mysql")]
DbClient::Mysql(pool) => {
crate::engines::mysql::history::insert_applied_migration(
pool,
schema,
table,
version,
description,
migration_type,
script,
checksum,
installed_by,
execution_time,
success,
)
.await
}
}
}
pub async fn has_entries_db(client: &DbClient, schema: &str, table: &str) -> Result<bool> {
match client {
#[cfg(feature = "postgres")]
DbClient::Postgres(c) => {
crate::engines::postgres::history::has_entries(c, schema, table).await
}
#[cfg(feature = "mysql")]
DbClient::Mysql(pool) => {
crate::engines::mysql::history::has_entries(pool, schema, table).await
}
}
}
pub async fn delete_failed_migrations_db(
client: &DbClient,
schema: &str,
table: &str,
) -> Result<u64> {
match client {
#[cfg(feature = "postgres")]
DbClient::Postgres(c) => {
crate::engines::postgres::history::delete_failed_migrations(c, schema, table).await
}
#[cfg(feature = "mysql")]
DbClient::Mysql(pool) => {
crate::engines::mysql::history::delete_failed_migrations(pool, schema, table).await
}
}
}
pub async fn update_checksum_db(
client: &DbClient,
schema: &str,
table: &str,
version: &str,
new_checksum: i32,
) -> Result<()> {
match client {
#[cfg(feature = "postgres")]
DbClient::Postgres(c) => {
crate::engines::postgres::history::update_checksum(
c,
schema,
table,
version,
new_checksum,
)
.await
}
#[cfg(feature = "mysql")]
DbClient::Mysql(pool) => {
crate::engines::mysql::history::update_checksum(
pool,
schema,
table,
version,
new_checksum,
)
.await
}
}
}
pub async fn update_repeatable_checksum_db(
client: &DbClient,
schema: &str,
table: &str,
script: &str,
new_checksum: i32,
) -> Result<()> {
match client {
#[cfg(feature = "postgres")]
DbClient::Postgres(c) => {
crate::engines::postgres::history::update_repeatable_checksum(
c,
schema,
table,
script,
new_checksum,
)
.await
}
#[cfg(feature = "mysql")]
DbClient::Mysql(pool) => {
crate::engines::mysql::history::update_repeatable_checksum(
pool,
schema,
table,
script,
new_checksum,
)
.await
}
}
}
pub fn effective_applied_versions(
applied: &[AppliedMigration],
) -> std::collections::HashSet<String> {
let mut effective = std::collections::HashSet::new();
for am in applied {
if !am.success {
continue;
}
if let Some(ref version) = am.version {
if am.migration_type == "UNDO_SQL" {
effective.remove(version);
} else {
effective.insert(version.clone());
}
}
}
effective
}