Repository: diesel-rs/diesel
File: diesel/src/connection/transaction_manager.rs (Rust)

Code before:
    let commit_error_outcome = conn.process_commit_error(error);
    let state = AnsiTransactionManager::get_transaction_state(conn)?;
    match commit_error_outcome {
        CommitErrorOutcome::RollbackAndThrow(error) => {
            // We should try to rollback the transaction here
            let rollback_result = conn.batch_execute(&*rollback_sql);
            let state = AnsiTransactionManager::get_transaction_state(conn)?;
            let rollback_result = state
                .change_transaction_depth(TransactionDepthChange::DecreaseDepth, rollback_result);
            Err(Error::CommitTransactionFailed {
                commit_error: Box::new(error),
                rollback_result: Box::new(rollback_result),
            })
        }
        CommitErrorOutcome::Throw(error) => {
            // The error processor indicated that we just
            // need to decrease the transaction depth and return the original error
            let _ = state.change_transaction_depth(TransactionDepthChange::DecreaseDepth, Ok(()));
            Err(Error::CommitTransactionFailed {
                commit_error: Box::new(error),
                rollback_result: Box::new(Ok(())),
            })
        }
        CommitErrorOutcome::ThrowAndMarkManagerAsBroken(error) => {
            // The connection contains an unrecoverable broken transaction
            // so mark the transaction state as broken and return the error
            *AnsiTransactionManager::transaction_manager_status_mut(conn) =
                TransactionManagerStatus::InError;
            Err(Error::CommitTransactionFailed {
                commit_error: Box::new(error),
                rollback_result: Box::new(Err(Error::BrokenTransaction)),
            })
        }
    }
}

#[cfg(test)]
mod test {
    use std::num::NonZeroU32;

    // Mock connection. Uses the Postgres backend for type signatures.
    // This is really because implementing a new backend is pain
    mod mock {
        use crate::connection::commit_error_processor::{CommitErrorOutcome, CommitErrorProcessor};
        use crate::connection::transaction_manager::AnsiTransactionManager;
        use crate::connection::{
            Connection, ConnectionGatWorkaround, SimpleConnection, TransactionManager,
        };
        use crate::expression::QueryMetadata;
        use crate::query_builder::{AsQuery, QueryFragment, QueryId};
        use crate::result::{Error, QueryResult};

Review comment (bug):
~~I know this is out of scope for this PR, but I'm curious what callers/users of the transaction manager are supposed to do when the manager is in such an error state. I admit that I tend to think about this from the far out perspective of a typical client -> backend -> DB perspective... in which case likely the backend would treat the `Throw` and `ThrowAndMarkMAnagerAsBroken` equally to tell the client "please try again"..~~

~~But the fact that both these enums exist, means that there's some use in both of them -- I'm particularly interested in the `ThrowAndMarkMAnagerAsBroken`... does this imply that it's possible that the backend (in my example) can somehow clean itself up? If so, how would it do that??~~

Nvmd I see this 😄 

https://github.com/diesel-rs/diesel/blob/ad79c7260d35616b5ddda4ae58ee1a23dc02c49c/diesel/src/connection/transaction_manager.rs#L58-L59

Though that does make me wonder, this seems like an unrecoverable error? Does `diesel` provide some kind of self-healing? Or would this be up to the caller/user of the `diesel` library to perhaps, first, propogate the error up to _its caller_ and, second, create a new transaction manager somehow? Probably "yes" now that I say it out loud 😅 

Code after:
    match commit_error_outcome {
        CommitErrorOutcome::RollbackAndThrow(error) => {
            // We should try to rollback the transaction here
            let rollback_result = conn.batch_execute(&*rollback_sql);
            let state = AnsiTransactionManager::get_transaction_state(conn)?;
            let rollback_result = state
                .change_transaction_depth(TransactionDepthChange::DecreaseDepth, rollback_result);
            Err(Error::CommitTransactionFailed {
                commit_error: Box::new(error),
                rollback_result: Box::new(rollback_result),
            })
        }
        CommitErrorOutcome::Throw(error) => {
            // The error processor indicated that we just
            // need to decrease the transaction depth and return the original error
            let _ = state.change_transaction_depth(TransactionDepthChange::DecreaseDepth, Ok(()));
            Err(Error::CommitTransactionFailed {
                commit_error: Box::new(error),
                rollback_result: Box::new(Ok(())),
            })
        }
        CommitErrorOutcome::ThrowAndMarkManagerAsBroken(error) => {
            // The connection contains an unrecoverable broken transaction
            // so mark the transaction state as broken and return the error
            *AnsiTransactionManager::transaction_manager_status_mut(conn) =
                TransactionManagerStatus::InError;
            Err(Error::CommitTransactionFailed {
                commit_error: Box::new(error),
                rollback_result: Box::new(Err(Error::BrokenTransaction)),
            })
        }
    }
}

#[cfg(test)]
mod test {
    // Mock connection.
    mod mock {
        use crate::connection::commit_error_processor::{CommitErrorOutcome, CommitErrorProcessor};
        use crate::connection::transaction_manager::AnsiTransactionManager;
        use crate::connection::{
            Connection, ConnectionGatWorkaround, SimpleConnection, TransactionManager,
        };
        use crate::expression::QueryMetadata;
        use crate::query_builder::{AsQuery, QueryFragment, QueryId};
        use crate::result::{Error, QueryResult};
        use crate::test_helpers::TestConnection;

        pub(crate) struct MockConnection {
            pub(crate) next_result: Option<QueryResult<usize>>,
            pub(crate) next_batch_execute_result: Option<QueryResult<()>>,