admin管理员组

文章数量:1122846

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?;
    txmit().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
}

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
}
Share Improve this question asked Nov 21, 2024 at 10:30 imbolcimbolc 1,8131 gold badge22 silver badges33 bronze badges 1
  • I think a live PostgreSQL database is necessary to compile the example, but switching query_scalar! to query_scalar is a step towards making it work without one. – Finn Bear Commented Nov 22, 2024 at 4:21
Add a comment  | 

2 Answers 2

Reset to default 1

While the impl-trait type (impl PgExecutor<'_>) is sufficient for executing queries, nothing about that type indicates to Rust that it may be reused or duplicated. One way to solve this is to specify the concrete type (PgConnection) and use a mutable reference:

use sqlx::{PgConnection, 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: &mut PgConnection) -> sqlx::Result<()> {
    dbg!(inner(db, "first").await?);
    dbg!(inner(db, "second").await?);
    Ok(())
}

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

The borrow checker is able to understand that, while &mut PgConnection can't arbitrary be duplicated, it can be reused for a second call to inner once the first call to inner is done.

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`

本文标签: rustHow to pass sqlxExecutor to nested futuresStack Overflow