SeaQL/sea-orm · error
There is uncommitted nested transaction
Error message
There is uncommitted nested transaction
What it means
`into_transaction()` converts the accumulated mock statement log into a `Transaction`, but only when `transaction_depth` is 0 — i.e. all nested (SAVEPOINT) transactions have been committed or rolled back. If nested transactions are still open, flushing the log would lose/ambiguate statements, so it panics. Typically reached when a `MockExecResult`/statement collector is finalized (e.g. at drop or end of test) with unbalanced nesting.
Source
Thrown at src/database/mock.rs:434
true
} else {
self.push(Statement::from_string(
db_backend,
format!("ROLLBACK TO SAVEPOINT savepoint_{}", self.transaction_depth),
));
self.transaction_depth -= 1;
false
}
}
fn push(&mut self, stmt: Statement) {
self.stmts.push(stmt);
}
fn into_transaction(self) -> Transaction {
match self.transaction_depth {
0 => Transaction { stmts: self.stmts },
_ => panic!("There is uncommitted nested transaction"),
}
}
}
#[cfg(test)]
#[cfg(feature = "mock")]
mod tests {
#[cfg(feature = "sync")]
use crate::util::StreamShim;
use crate::{
DbBackend, DbErr, IntoMockRow, MockDatabase, Statement, Transaction, TransactionError,
TransactionTrait, entity::*, error::*, tests_cfg::*,
};
// In the sync variant `StreamShim` provides `try_next`; `futures_util` isn't a dependency there.
#[cfg(not(feature = "sync"))]
use futures_util::TryStreamExt;
use pretty_assertions::assert_eq;
View on GitHub (pinned to e29bcd1b41)
Solutions
- Commit or rollback every nested transaction before calling `into_transaction()`.
- Count begins vs commits/rollbacks in the test script; they must balance to depth 0.
- Fix early-return paths in the code under test so inner transactions always close (RAII/guard pattern).
- If inspecting the log mid-test, do it after all nesting has unwound.
Example fix
// before begin(stmt), begin(stmt), commit(stmt) into_transaction() // panics: depth still 1 // after begin(stmt), begin(stmt), commit(stmt), commit(stmt) into_transaction() // ok: depth 0
Defensive patterns
Strategy: validation
Validate before calling
assert_eq!(depth, 0, "cannot finalize with uncommitted nested transactions"); let tx = collector.into_transaction();
Type guard
fn can_finalize(c: &StatementCollector) -> bool {
c.transaction_depth == 0
} Try / catch
std::panic::catch_unwind(|| collector.into_transaction()) // prefer balancing nesting first
Prevention
- Ensure every begin (savepoint) has a matching commit/rollback before finalizing.
- Count begin vs commit/rollback calls in test harnesses.
- Use scopeguard/RAII so nested transactions close on early return.
- Only drain/inspect the transaction log after all nesting unwound.
When it happens
Trigger: Calling `into_transaction()` (directly or via drain/finalize paths) while `transaction_depth > 0`: a `begin` was issued but its matching `commit`/`rollback` never ran.
Common situations: Tests simulating nested transactions where one nesting level's commit was forgotten; early-return in code under test skipping the inner commit; asserting on the transaction log before closing all savepoints.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- There is uncommitted nested transaction
- There is no open transaction to commit
- There is no open transaction to rollback
- There is no open transaction to commit
- There is no open transaction to rollback
AI-assisted analysis of SeaQL/sea-orm@e29bcd1b41 (2026-09-10).
Data as JSON: /api/errors/552d7c10b97b7ae9.
Report an issue: GitHub.