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

I want to encapsulate a function that executes a transaction, and the transaction execution body is passed in as a closure, but an error is reported, ask for help #3543

Closed tgy3300 closed 1 month ago

tgy3300 commented 1 month ago
async fn execute_transaction<F, Fut>(
    conn: Pool<MySql>,
    f: F,
) -> Result<(), Box<dyn std::error::Error>>
where
    F: FnOnce(&mut Transaction<'_, MySql>) -> Fut,
    Fut: Future<Output = Result<(), Box<dyn std::error::Error>>>,
{
    let mut txn = conn.begin().await?;

    f(&mut txn).await?;

    txn.commit().await?;

    Ok(())
}

async fn test_fn() -> Result<(), Box<dyn std::error::Error>> {
    let conn = get_mysql_pool() .await;

    execute_transaction(conn, |txn| async {
        let r1 = sqlx::query("DELETE FROM blog WHERE id = ?")
            .bind("aaa")
            .execute(txn)
            .await?
            .rows_affected();

        let r2 = sqlx::query("DELETE FROM book WHERE id = ?")
            .bind("bbb")
            .execute(txn)
            .await?
            .rows_affected();

        Ok(())
    })
    .await?;

    Ok(())
}

Report an error:


error[E0277]: the trait bound `&mut Transaction<'_, MySql>: Executor<'_>` is not satisfied
   --> src\units\model\tidb\mod.rs:446:18
    |
446 |           let r1 = sqlx::query("DELETE FROM bolg WHERE id = ?")
    |  __________________^
447 | |             .bind("aaa")
448 | |             .execute(txn)
    | |_________________________^ the trait `Executor<'_>` is not implemented for `&mut Transaction<'_, MySql>`
    |
    = help: the following other types implement trait `Executor<'c>`:
              `&'c mut AnyConnection` implements `Executor<'c>`
              `&'c mut MySqlConnection` implements `Executor<'c>`
              `&sqlx::Pool<DB>` implements `Executor<'p>`
note: required by a bound in `sqlx::query::Query::<'q, DB, A>::execute`
   --> C:\Users\Administrator\.cargo\registry\src\rsproxy.cn-0dccff568467c15b\sqlx-core-0.8.2\src\query.rs:188:12
    |
184 |     pub async fn execute<'e, 'c: 'e, E>(self, executor: E) -> Result<DB::QueryResult, Error>
    |                  ------- required by a bound in this associated function
...
188 |         E: Executor<'c, Database = DB>,
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `Query::<'q, DB, A>::execute`
CommanderStorm commented 1 month ago

the lifetime issue that I am hitting after fixing the dereferencing issue (.execute(&mut *txn)) in your example is a bit tricky and I have not really found a way to tell the borrow checker that the lifetimes are actually fine.

The following would be how I could get around this

#[cfg(test)]
mod tests {
    use sqlx::MySql;
    use sqlx::MySqlPool;
    use sqlx::Transaction;
    use std::future::Future;
    use sqlx::Error;

    async fn execute_transaction<'a,'tx:'a,F, Fut>(
        conn: MySqlPool,
        f: F,
    ) -> Result<(), Error>
    where
        F: FnOnce(Transaction<'tx, MySql>) -> Fut,
        Fut: Future<Output = Result<(), Error>> {
        let txn = conn.begin().await?;
        f(txn).await
    }

    #[tokio::test]
    async fn test_fn() -> anyhow::Result<()> {
        let conn = MySqlPool::connect("url").await?;

        execute_transaction(conn, |mut txn| async move {
            let _r1 = sqlx::query("DELETE FROM blog WHERE id = ?")
                .bind("aaa")
                .execute(&mut *txn)
                .await?
                .rows_affected();

            txn.commit().await
        })
        .await?;
        Ok(())
    }
}

Because the API with the closures is not really better without explicite support, I think you might be better off by just doing it explcitely https://github.com/launchbadge/sqlx/blob/19f40d87a669e41081a629a469359d341c9223d6/examples/postgres/transaction/src/main.rs#L52-L63

abonander commented 1 month ago

Issues are not for asking for support with your own code. Please use Discussions or Discord.