rustrust-sqlx

How to pass `sqlx::Executor` to nested futures


I'm trying to make functions work with both PgPool and PgTransaction. The PgExecutor seems to be meant for this. But I can't understand how to pass it around. It's implemented for PgPool, which is Clone and PgConnection which isn't, so I can't just add the Clone bound.

Here's a runnable example:

use sqlx::{PgExecutor, PgPool};

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    let pool = PgPool::connect("postgres:///").await?;

    let mut tx = pool.begin().await?;
    outer(&mut *tx).await?;
    tx.commit().await
}

async fn outer(db: impl PgExecutor<'_>) -> sqlx::Result<()> {
    dbg!(inner(db, "first").await?);

    // The second invocation doesn't compile:
    // use of moved value: `db`  value used here after move
    dbg!(inner(db, "second").await?);

    Ok(())
}

async fn inner(db: impl PgExecutor<'_>, name: &str) -> sqlx::Result<String> {
    sqlx::query_scalar!(r#"SELECT $1 as "name!""#, name)
        .fetch_one(db)
        .await
}

Solution

  • Jofas from the Rust Forum found the solution that allows both outer and inner be compatible with the PgPool and PgConnection:

    use sqlx::{Acquire, PgExecutor, PgPool, Postgres};
    
    #[tokio::main]
    async fn main() -> Result<(), sqlx::Error> {
        let pool = PgPool::connect("postgres:///").await?;
    
        let mut tx = pool.begin().await?;
        outer(&mut *tx).await?;
        tx.commit().await
    }
    
    async fn outer(db: impl Acquire<'_, Database = Postgres>) -> sqlx::Result<()> {
        let mut connection = db.acquire().await?;
    
        dbg!(inner(&mut *connection, "first").await?);
        dbg!(inner(&mut *connection, "second").await?);
    
        Ok(())
    }
    
    async fn inner(db: impl PgExecutor<'_>, name: &str) -> sqlx::Result<String> {
        sqlx::query_scalar!(r#"SELECT $1 as "name!""#, name)
            .fetch_one(db)
            .await
    }
    

    Though in complicated scenarios, there's a lifetime issue with the Acquire approach, forcing you to resort to the Connection-based approach:

    implementation of `sqlx::Acquire` is not general enough
    = note: `sqlx::Acquire<'_>` would have to be implemented for the type `&mut PgConnection`
    = note: ...but `sqlx::Acquire<'0>` is actually implemented for the type `&'0 mut PgConnection`, for some specific lifetime `'0`