launchbadge / sqlx

🧰 The Rust SQL Toolkit. An async, pure Rust SQL crate featuring compile-time checked queries without a DSL. Supports PostgreSQL, MySQL, and SQLite.
Apache License 2.0
13.46k stars 1.28k forks source link

Feature Request: Expose `transaction_depth` with a Read-Only Getter Method #3426

Open mpyw opened 3 months ago

mpyw commented 3 months ago

Is your feature request related to a problem? Please describe.

transaction_depth is currently marked as pub(crate) in the connection struct, which makes it inaccessible from outside the crate. This limitation poses a challenge when working with advisory locks, as described in the following library's README:

mpyw/laravel-database-advisory-lock: Advisory Locking Features for Postgres/MySQL/MariaDB on Laravel

Session-level advisory locks must be acquired before entering a transaction and released only after the transaction has been committed. To ensure consistency, it's crucial to verify whether a transaction is already active before acquiring the lock.

Describe the solution you'd like

I would like to request a read-only Getter method for the transaction_depth field to be added, so that it can be accessed from outside the crate.

Describe alternatives you've considered

An alternative could be to use a custom wrapper around the connection, but this would add unnecessary complexity and overhead. Another option might be to manage transaction depth manually in user code, but this is error-prone and defeats the purpose of the existing transaction_depth field.

Additional context

Providing access to transaction_depth would allow for safer and more consistent handling of nested transactions, particularly in scenarios involving advisory locks.

mpyw commented 3 months ago

The trait method signature would be:

/// Returns the current transaction depth.
///
/// Transaction depth indicates the level of nested transactions:
/// - Level 0: No active transaction.
/// - Level 1: A transaction is active.
/// - Level 2 or higher: A transaction is active and one or more SAVEPOINTs have been created within it.
fn get_transaction_depth(&self) -> BoxFuture<'_, Result<usize, Error>>;

PgConnection MySqlConnection are easy to be inplemented:

// MySqlConnection
fn get_transaction_depth(&self) -> usize {
    self.inner.transaction_depth
}
// PgConnection
fn get_transaction_depth(&self) -> usize {
    self.transaction_depth
}

But SqliteConnection requires asynchronously locking:

// SqliteConnection
async fn get_transaction_depth(&mut self) -> Result<usize, Error> {
    Ok(self.lock_handle().await?.guard.transaction_depth)
}

How should we achive this?

mpyw commented 3 months ago

Trait splitting?

pub trait TransactionDepth {
    /// Returns the current transaction depth synchronously.
    ///
    /// Transaction depth indicates the level of nested transactions:
    /// - Level 0: No active transaction.
    /// - Level 1: A transaction is active.
    /// - Level 2 or higher: A transaction is active and one or more SAVEPOINTs have been created within it.
    fn get_transaction_depth(&self) -> usize;
}

pub trait AsyncTransactionDepth {
    /// Returns the current transaction depth asynchronously.
    ///
    /// Transaction depth indicates the level of nested transactions:
    /// - Level 0: No active transaction.
    /// - Level 1: A transaction is active.
    /// - Level 2 or higher: A transaction is active and one or more SAVEPOINTs have been created within it.
    fn get_transaction_depth(&mut self) -> BoxFuture<'_, Result<usize, Error>>;
}
impl TransactionDepth for PgConnection {
    fn get_transaction_depth(&self) -> usize {
        self.transaction_depth
    }
}

impl TransactionDepth for MySqlConnection {
    fn get_transaction_depth(&self) -> usize {
        self.inner.transaction_depth
    }
}

impl AsyncTransactionDepth for SqliteConnection {
    fn get_transaction_depth(&mut self) -> BoxFuture<'_, Result<usize, Error>> {
        Box::pin(async move {
            Ok(self.lock_handle().await?.guard.transaction_depth)
        })
    }
}