{"record":{"id":"552d7c10b97b7ae9","repo":"SeaQL/sea-orm","slug":"there-is-uncommitted-nested-transaction-552d7c","errorCode":null,"errorMessage":"There is uncommitted nested transaction","messagePattern":"There is uncommitted nested transaction","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/database/mock.rs","lineNumber":434,"sourceCode":"            true\n        } else {\n            self.push(Statement::from_string(\n                db_backend,\n                format!(\"ROLLBACK TO SAVEPOINT savepoint_{}\", self.transaction_depth),\n            ));\n            self.transaction_depth -= 1;\n            false\n        }\n    }\n\n    fn push(&mut self, stmt: Statement) {\n        self.stmts.push(stmt);\n    }\n\n    fn into_transaction(self) -> Transaction {\n        match self.transaction_depth {\n            0 => Transaction { stmts: self.stmts },\n            _ => panic!(\"There is uncommitted nested transaction\"),\n        }\n    }\n}\n\n#[cfg(test)]\n#[cfg(feature = \"mock\")]\nmod tests {\n    #[cfg(feature = \"sync\")]\n    use crate::util::StreamShim;\n    use crate::{\n        DbBackend, DbErr, IntoMockRow, MockDatabase, Statement, Transaction, TransactionError,\n        TransactionTrait, entity::*, error::*, tests_cfg::*,\n    };\n    // In the sync variant `StreamShim` provides `try_next`; `futures_util` isn't a dependency there.\n    #[cfg(not(feature = \"sync\"))]\n    use futures_util::TryStreamExt;\n    use pretty_assertions::assert_eq;\n","sourceCodeStart":416,"sourceCodeEnd":452,"githubUrl":"https://github.com/SeaQL/sea-orm/blob/e29bcd1b417c41a553b386fe94511d7c64a1c8ec/src/database/mock.rs#L416-L452","documentation":"`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.","triggerScenarios":"Calling `into_transaction()` (directly or via drain/finalize paths) while `transaction_depth > 0`: a `begin` was issued but its matching `commit`/`rollback` never ran.","commonSituations":"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.","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."],"exampleFix":"// before\nbegin(stmt), begin(stmt), commit(stmt)\ninto_transaction() // panics: depth still 1\n\n// after\nbegin(stmt), begin(stmt), commit(stmt), commit(stmt)\ninto_transaction() // ok: depth 0","handlingStrategy":"validation","validationCode":"assert_eq!(depth, 0, \"cannot finalize with uncommitted nested transactions\");\nlet tx = collector.into_transaction();","typeGuard":"fn can_finalize(c: &StatementCollector) -> bool {\n    c.transaction_depth == 0\n}","tryCatchPattern":"std::panic::catch_unwind(|| collector.into_transaction()) // prefer balancing nesting first","preventionTips":["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."],"tags":["panic","transaction","mock","nested-transactions"],"backgroundTag":"invalid-state-transition","analyzedSha":"e29bcd1b417c41a553b386fe94511d7c64a1c8ec","analyzedAt":"2026-09-10T11:31:52.468Z","contentChangedAt":"2026-09-10T11:31:52.468Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}