mod common;
use common::migrator::*;
use sea_orm::{ConnectOptions, ConnectionTrait, Database, DbBackend, DbErr, Statement};
use sea_orm_migration::{MigratorTraitSelf, migrator::MigrationStatus, prelude::*};
#[tokio::test]
async fn main() -> Result<(), DbErr> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.with_test_writer()
.init();
let url = &std::env::var("DATABASE_URL").expect("Environment variable 'DATABASE_URL' not set");
run_migration(url, default::Migrator, "sea_orm_migration", "public").await?;
run_migration(
url,
default::Migrator,
"sea_orm_migration_schema",
"my_schema",
)
.await?;
run_migration(
url,
with_self::Migrator { i: 12 },
"sea_orm_migration_self",
"public",
)
.await?;
run_migration(
url,
override_migration_table_name::Migrator,
"sea_orm_migration_table_name",
"public",
)
.await?;
run_migration(
url,
override_migration_table_name::Migrator,
"sea_orm_migration_table_name_schema",
"my_schema",
)
.await?;
run_transaction_test(url, "sea_orm_migration_txn", "public").await?;
Ok(())
}
async fn create_db(
url: &str,
db_name: &str,
schema: &str,
) -> Result<sea_orm::DatabaseConnection, DbErr> {
let db_connect = |url: String| async {
let connect_options = ConnectOptions::new(url)
.set_schema_search_path(format!("{schema},public"))
.to_owned();
Database::connect(connect_options).await
};
let db = db_connect(url.to_owned()).await?;
match db.get_database_backend() {
DbBackend::MySql => {
db.execute_raw(Statement::from_string(
db.get_database_backend(),
format!("CREATE DATABASE IF NOT EXISTS `{db_name}`;"),
))
.await?;
let url = format!("{url}/{db_name}");
db_connect(url).await
}
DbBackend::Postgres => {
db.execute_raw(Statement::from_string(
db.get_database_backend(),
format!("DROP DATABASE IF EXISTS \"{db_name}\";"),
))
.await?;
db.execute_raw(Statement::from_string(
db.get_database_backend(),
format!("CREATE DATABASE \"{db_name}\";"),
))
.await?;
let url = format!("{url}/{db_name}");
let db = db_connect(url).await?;
db.execute_raw(Statement::from_string(
db.get_database_backend(),
format!("CREATE SCHEMA IF NOT EXISTS \"{schema}\";"),
))
.await?;
Ok(db)
}
DbBackend::Sqlite => Ok(db),
db => Err(DbErr::BackendNotSupported {
db: db.as_str(),
ctx: "create_db",
}),
}
}
async fn run_migration<M>(url: &str, migrator: M, db_name: &str, schema: &str) -> Result<(), DbErr>
where
M: MigratorTraitSelf,
{
let db = &create_db(url, db_name, schema).await?;
let manager = SchemaManager::new(db);
println!("\nMigrator::status");
migrator.status(db).await?;
println!("\nMigrator::install");
migrator.install(db).await?;
let migration_table_name = migrator.migration_table_name().to_string();
let migration_table_name = migration_table_name.as_str();
assert!(manager.has_table(migration_table_name).await?);
if migration_table_name != "seaql_migrations" {
assert!(!manager.has_table("seaql_migrations").await?);
}
println!("\nMigrator::reset");
migrator.reset(db).await?;
assert!(!manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);
println!("\nMigrator::up");
migrator.up(db, Some(0)).await?;
assert!(!manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);
println!("\nMigrator::up");
migrator.up(db, Some(1)).await?;
println!("\nMigrator::get_pending_migrations");
let migrations = migrator.get_pending_migrations(db).await?;
assert_eq!(migrations.len(), 5);
let migration = migrations.get(0).unwrap();
assert_eq!(migration.name(), "m20220118_000002_create_fruit_table");
assert_eq!(migration.status(), MigrationStatus::Pending);
assert!(manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);
println!("\nMigrator::down");
migrator.down(db, Some(0)).await?;
assert!(manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);
println!("\nMigrator::down");
migrator.down(db, Some(1)).await?;
assert!(!manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);
if matches!(db.get_database_backend(), DbBackend::Postgres) {
println!("\nRoll back changes when encounter errors");
unsafe {
std::env::set_var("ABORT_MIGRATION", "YES");
}
println!("\nMigrator::up");
assert_eq!(
migrator.up(db, None).await,
Err(DbErr::Migration(
"Abort migration and rollback changes".into()
))
);
println!("\nMigrator::status");
migrator.status(db).await?;
assert!(manager.has_table("cake").await?);
assert!(manager.has_table("fruit").await?);
unsafe {
std::env::remove_var("ABORT_MIGRATION");
}
}
println!("\nMigrator::up");
migrator.up(db, None).await?;
println!("\nMigrator::get_applied_migrations");
let migrations = migrator.get_applied_migrations(db).await?;
assert_eq!(migrations.len(), 6);
assert!(!manager.has_index("cake", "non_existent_index").await?);
assert!(manager.has_index("cake", "cake_name_index").await?);
let migration = migrations.get(0).unwrap();
assert_eq!(migration.name(), "m20220118_000001_create_cake_table");
assert_eq!(migration.status(), MigrationStatus::Applied);
println!("\nMigrator::status");
migrator.status(db).await?;
assert!(manager.has_table("cake").await?);
assert!(manager.has_table("fruit").await?);
assert!(manager.has_column("cake", "name").await?);
assert!(manager.has_column("fruit", "cake_id").await?);
if matches!(db.get_database_backend(), DbBackend::Postgres) {
println!("\nRoll back changes when encounter errors");
unsafe {
std::env::set_var("ABORT_MIGRATION", "YES");
}
println!("\nMigrator::down");
assert_eq!(
migrator.down(db, None).await,
Err(DbErr::Migration(
"Abort migration and rollback changes".into()
))
);
println!("\nMigrator::status");
migrator.status(db).await?;
assert!(manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);
unsafe {
std::env::remove_var("ABORT_MIGRATION");
}
}
println!("\nMigrator::down");
migrator.down(db, None).await?;
assert!(manager.has_table(migration_table_name).await?);
if migration_table_name != "seaql_migrations" {
assert!(!manager.has_table("seaql_migrations").await?);
}
assert!(!manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);
println!("\nMigrator::fresh");
migrator.fresh(db).await?;
assert!(manager.has_table("cake").await?);
assert!(manager.has_table("fruit").await?);
println!("\nMigrator::refresh");
migrator.refresh(db).await?;
assert!(manager.has_table("cake").await?);
assert!(manager.has_table("fruit").await?);
println!("\nMigrator::reset");
migrator.reset(db).await?;
assert!(!manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);
println!("\nMigrator::status");
migrator.status(db).await?;
Ok(())
}
async fn run_transaction_test(url: &str, db_name: &str, schema: &str) -> Result<(), DbErr> {
let db = &create_db(url, db_name, schema).await?;
let backend = db.get_database_backend();
let manager = SchemaManager::new(db);
println!("\nTransaction test: use_transaction = None");
let m = transaction_test::Migrator {
use_transaction: None,
should_fail: false,
};
m.up(db, None).await?;
assert!(manager.has_table("test_table").await?);
m.down(db, None).await?;
assert!(!manager.has_table("test_table").await?);
m.reset(db).await.ok();
println!("\nTransaction test: use_transaction = Some(true)");
let m = transaction_test::Migrator {
use_transaction: Some(true),
should_fail: false,
};
m.up(db, None).await?;
assert!(manager.has_table("test_table").await?);
m.down(db, None).await?;
assert!(!manager.has_table("test_table").await?);
m.reset(db).await.ok();
println!("\nTransaction test: use_transaction = Some(false)");
let m = transaction_test::Migrator {
use_transaction: Some(false),
should_fail: false,
};
m.up(db, None).await?;
assert!(manager.has_table("test_table").await?);
m.down(db, None).await?;
assert!(!manager.has_table("test_table").await?);
m.reset(db).await.ok();
println!("\nTransaction test: failure with transaction");
let m = transaction_test::Migrator {
use_transaction: Some(true),
should_fail: true,
};
assert!(m.up(db, None).await.is_err());
if backend != DbBackend::MySql {
assert!(
!manager.has_table("test_table").await?,
"DDL should be rolled back"
);
}
m.reset(db).await.ok();
println!("\nTransaction test: failure without transaction");
let m = transaction_test::Migrator {
use_transaction: Some(false),
should_fail: true,
};
assert!(m.up(db, None).await.is_err());
assert!(manager.has_table("test_table").await?, "DDL should persist");
db.execute_unprepared("DROP TABLE IF EXISTS test_table")
.await?;
m.reset(db).await.ok();
println!("\nTransaction test: manual begin/commit");
let m = transaction_test::ManualTxnMigrator;
m.up(db, None).await?;
assert!(manager.has_table("manual_txn_table").await?);
m.down(db, None).await?;
assert!(!manager.has_table("manual_txn_table").await?);
m.reset(db).await.ok();
Ok(())
}