{"record":{"id":"3c739b6e4f9fc56f","repo":"SeaQL/sea-orm","slug":"there-is-uncommitted-nested-transaction","errorCode":null,"errorMessage":"There is uncommitted nested transaction","messagePattern":"There is uncommitted nested transaction","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sea-orm-sync/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/sea-orm-sync/src/database/mock.rs#L416-L452","documentation":"into_transaction() converts the accumulated mock statement buffer into a Transaction. It panics when transaction_depth != 0, meaning BEGIN/COMMIT pairs inside the buffered statements are unbalanced — the transaction was never fully committed (or rolled back), so the statement set is not a complete transaction.","triggerScenarios":"Draining the mock transaction log / calling into_transaction() while nested (savepoint) transactions are still open: a begin() without a matching commit(), or extra begins over commits.","commonSituations":"Application code opening nested transactions it never commits; test assertions run before the final commit; savepoint depth mismatch after an error path skipped a commit.","solutions":["Balance every begin() with a commit() or rollback() before draining the transaction log","Check nested-transaction depth handling in the code under test (savepoints must be committed in order)","Move the into_transaction()/log assertion after all transaction operations complete"],"exampleFix":"// before\nconn.begin();\nconn.execute(stmt).unwrap();\nlet log = db.into_transaction_log(); // panics: nested txn open\n// after\nconn.begin();\nconn.execute(stmt).unwrap();\nconn.commit();\nlet log = db.into_transaction_log(); // ok","handlingStrategy":"validation","validationCode":"// before draining the log, ensure all nested transactions are closed:\nconn.commit(); // closes the last open savepoint/transaction\nlet log = mock_db.into_transaction_log();","typeGuard":null,"tryCatchPattern":"std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| txn.into_transaction()))\n    .map_err(|_| anyhow!(\"uncommitted nested transaction before drain\"))?;","preventionTips":["Balance every begin/commit pair, including savepoints in nested-transaction code","Assert on the transaction log only after all operations (and their commits) complete","Review error paths that bail out mid-transaction and add rollback handling"],"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"}