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 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.

Source

Thrown at sea-orm-sync/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

  1. Balance every begin() with a commit() or rollback() before draining the transaction log
  2. Check nested-transaction depth handling in the code under test (savepoints must be committed in order)
  3. Move the into_transaction()/log assertion after all transaction operations complete

Example fix

// before
conn.begin();
conn.execute(stmt).unwrap();
let log = db.into_transaction_log(); // panics: nested txn open
// after
conn.begin();
conn.execute(stmt).unwrap();
conn.commit();
let log = db.into_transaction_log(); // ok
Defensive patterns

Strategy: validation

Validate before calling

// before draining the log, ensure all nested transactions are closed:
conn.commit(); // closes the last open savepoint/transaction
let log = mock_db.into_transaction_log();

Try / catch

std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| txn.into_transaction()))
    .map_err(|_| anyhow!("uncommitted nested transaction before drain"))?;

Prevention

When it happens

Trigger: 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.

Common situations: 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.

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


AI-assisted analysis of SeaQL/sea-orm@e29bcd1b41 (2026-09-10). Data as JSON: /api/errors/3c739b6e4f9fc56f. Report an issue: GitHub.