Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

- Update the declared MSRV of `sea-orm-cli` to Rust 1.94, matching its existing SQLx 0.9 dependency requirement https://github.com/SeaQL/sea-orm/pull/3187

- SQLite migrations now run in transactions by default

Existing SQLite migrations that change `PRAGMA foreign_keys` may be affected, as SQLite silently ignores such changes within a transaction.

## [2.0.4](changelog/2.0.4.md) - 2026-09-27

`select_except`, `save_as` casts for `eq_any` / `ne_all`, linked-join alias and `condition_type` fixes, MySQL schema-sync index drop fix, arrow 60
Expand Down
2 changes: 1 addition & 1 deletion sea-orm-migration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub trait MigrationTrait: MigrationName + Send + Sync {

/// Control whether this migration runs inside a transaction.
///
/// - `None` (default): follow backend convention (Postgres = transaction, MySQL/SQLite = no transaction)
/// - `None` (default): follow backend convention: use transaction on Postgres and SQLite
/// - `Some(true)`: force wrapping in a transaction on any backend
/// - `Some(false)`: disable automatic transaction wrapping (use `manager.begin()` for manual control)
fn use_transaction(&self) -> Option<bool> {
Expand Down
2 changes: 1 addition & 1 deletion sea-orm-migration/src/migrator/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ async fn drop_everything_impl<C: ConnectionTrait>(db: &C) -> Result<(), DbErr> {
fn should_use_transaction(migration: &dyn crate::MigrationTrait, backend: DbBackend) -> bool {
match migration.use_transaction() {
Some(v) => v,
None => backend == DbBackend::Postgres,
None => matches!(backend, DbBackend::Postgres | DbBackend::Sqlite),
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ impl MigrationTrait for Migration {
}

async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let expect_txn = self
.use_transaction
.unwrap_or(manager.get_database_backend() == DbBackend::Postgres);
let expect_txn = self.use_transaction.unwrap_or(matches!(
manager.get_database_backend(),
DbBackend::Postgres | DbBackend::Sqlite
));
assert_eq!(
manager.get_connection().is_transaction(),
expect_txn,
Expand All @@ -47,9 +48,10 @@ impl MigrationTrait for Migration {
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let expect_txn = self
.use_transaction
.unwrap_or(manager.get_database_backend() == DbBackend::Postgres);
let expect_txn = self.use_transaction.unwrap_or(matches!(
manager.get_database_backend(),
DbBackend::Postgres | DbBackend::Sqlite
));
assert_eq!(
manager.get_connection().is_transaction(),
expect_txn,
Expand Down
20 changes: 15 additions & 5 deletions sea-orm-migration/tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,10 +241,13 @@ where
assert!(!manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);

// Tests rolling back a failing migration on Postgres.
// Tests rolling back a failing migration on Postgres and SQLite.
// With per-migration transactions, only the failing migration is rolled back;
// earlier migrations that committed successfully are preserved.
if matches!(db.get_database_backend(), DbBackend::Postgres) {
if matches!(
db.get_database_backend(),
DbBackend::Postgres | DbBackend::Sqlite
) {
println!("\nRoll back changes when encounter errors");

// Set a flag to throw error inside `m20230109_000001_seed_cake_table.rs`
Expand All @@ -269,6 +272,8 @@ where
assert!(manager.has_table("cake").await?);
assert!(manager.has_table("fruit").await?);

assert_eq!(migrator.get_applied_migrations(db).await?.len(), 5);

// Unset the flag
unsafe {
std::env::remove_var("ABORT_MIGRATION");
Expand Down Expand Up @@ -298,12 +303,15 @@ where
assert!(manager.has_column("cake", "name").await?);
assert!(manager.has_column("fruit", "cake_id").await?);

// Tests rolling back a failing migration-down on Postgres.
// Tests rolling back a failing migration-down on Postgres and SQLite.
// With per-migration transactions, rollbacks happen one at a time in reverse.
// Migrations 6-2 roll back and commit successfully. Migration 1 (drops cake
// then ABORTs) fails, so its DROP is restored. But migration 2's DROP of
// the fruit table already committed.
if matches!(db.get_database_backend(), DbBackend::Postgres) {
if matches!(
db.get_database_backend(),
DbBackend::Postgres | DbBackend::Sqlite
) {
println!("\nRoll back changes when encounter errors");

// Set a flag to throw error inside `m20220118_000001_create_cake_table.rs`
Expand All @@ -328,6 +336,8 @@ where
assert!(manager.has_table("cake").await?);
assert!(!manager.has_table("fruit").await?);

assert_eq!(migrator.get_applied_migrations(db).await?.len(), 1);

// Unset the flag
unsafe {
std::env::remove_var("ABORT_MIGRATION");
Expand Down Expand Up @@ -374,7 +384,7 @@ async fn run_transaction_test(url: &str, db_name: &str, schema: &str) -> Result<
let backend = db.get_database_backend();
let manager = SchemaManager::new(db);

// use_transaction = None: Postgres wraps by default, others don't.
// use_transaction = None: Postgres and SQLite wrap by default.
// The assertion happens inside the migration's up()/down() body.
println!("\nTransaction test: use_transaction = None");
let m = transaction_test::Migrator {
Expand Down
Loading