## Release Notes: SeaORM 2.0.0-rc.36 *(since 2.0.0-rc.35)* ### New Features **Per-Migration Transaction Control** (#2980) Previously, all Postgres migrations ran inside a single batch transaction, while MySQL and SQLite ran without one. This was an all-or-nothing approach with no way to opt out for individual migrations (e.g. `CREATE INDEX CONCURRENTLY` on Postgres requires running outside a transaction). `MigrationTrait` now has a `use_transaction()` method to control this per migration: ```rust impl MigrationTrait for Migration { fn use_transaction(&self) -> Option { Some(false) // opt out of automatic transaction } } ``` - `None` (default): follow backend convention — Postgres uses a transaction, MySQL/SQLite do not - `Some(true)`: force a transaction on any backend - `Some(false)`: disable automatic transaction wrapping For migrations that opt out, `SchemaManager::begin()` and `SchemaManager::commit()` allow manual transaction control within the migration body: ```rust async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { // DDL in a transaction let m = manager.begin().await?; m.create_table( Table::create() .table("my_table") .col(pk_auto("id")) .col(string("name")) .to_owned(), ).await?; m.commit().await?; // Non-transactional DDL manager.get_connection() .execute_unprepared("CREATE INDEX CONCURRENTLY idx_name ON my_table (name)") .await?; Ok(()) } ``` Core changes: - Added `OwnedTransaction` variant to `DatabaseExecutor`, enabling `SchemaManager` to own a transaction - Added `DatabaseExecutor::is_transaction()` for runtime introspection - Each migration is now wrapped individually rather than in a batch