0

I need to generalize functions to pass an Executor trait for SQLX code. In the code below with a concrete &mut SqliteConnection parameter, main can call process, or it can call process_twice which calls process 2x times. All sqlx functions require arg type E: Executor.

I need to make my code generic so that conn: &mut SqliteConnection arg is also written with some generic, but so i can use it more than once.

Inside Sqlx, multiple structs implement Executor trait on a mutable reference, e.g.

impl<'c> Executor<'c> for &'c mut SqliteConnection {...}

I was able to convert a SINGLE call (the fn process), but not the fn process_twice - because executor is not copyable.

async fn process<'a, E>(conn: E) -> anyhow::Result<()>
where E: sqlx::Executor<'a, Database = sqlx::Sqlite> {...}

Full example

// [dependencies]
// anyhow = "1.0"
// futures = "0.3"
// sqlx = { version = "0.6", features = [ "sqlite", "runtime-tokio-native-tls"] }
// tokio = { version = "1.28.2", features = ["macros"] }
//
//  //// TO RUN, must set env var:
// DATABASE_URL='sqlite::memory:' cargo run

use futures::TryStreamExt;
use sqlx::SqliteConnection;
use sqlx::{query, Connection};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut conn = SqliteConnection::connect("sqlite::memory:").await?;
    process(&mut conn).await?;
    process_twice(&mut conn).await?;
    Ok(())
}

async fn process(conn: &mut SqliteConnection) -> anyhow::Result<()> {
    let sql = query!("SELECT name FROM sqlite_master");
    let mut rows = sql.fetch(conn);
    while let Some(row) = rows.try_next().await? {
        println!("{row:?}")
    }
    Ok(())
}

async fn process_twice(conn: &mut SqliteConnection) -> anyhow::Result<()> {
    process(conn).await?;
    process(conn).await?;
    Ok(())
}

Similar questions: this

Yuri Astrakhan
  • 8,808
  • 6
  • 63
  • 97
  • Seems to me like you actually want a type implementing `Connection` – cafce25 Jun 03 '23 at 06:07
  • @cafce25 it seems `fetch(...)` and other only accept Executor, see https://github.com/launchbadge/sqlx/blob/253d8c9f696a3a2c7aa837b04cc93605a1376694/sqlx-core/src/query.rs#L172 – Yuri Astrakhan Jun 03 '23 at 06:18
  • So? You can use [`transaction`](https://docs.rs/sqlx/latest/sqlx/trait.Connection.html#method.transaction) to get access to a `Transaction` which implements `Executor` – cafce25 Jun 03 '23 at 06:21
  • @cafce25 I might be missing something - I need my functions to accept `&mut` to `PoolConnection` and `SqliteConnection` instances transparently. Functions make a bunch of read-only calls, so trx might cause a delay(?), but more importantly, I don't want functions to be cross-dependent, to know if trx has started, etc – Yuri Astrakhan Jun 03 '23 at 06:25

1 Answers1

2

The trick is to not parametrize the whole E but just the type behind the reference:

async fn process_twice<T>(conn: &mut T) -> anyhow::Result<()>
where
    for<'e> &'e mut T: Executor<'e, Database = Sqlite>,
{
    process(&mut *conn).await?;
    process(conn).await
}

That way you can still reborrow the reference. That does mean that you can't take Pool<DB> any more because it only implements Executor for &Pool but should work for your usecase.

cafce25
  • 15,907
  • 4
  • 25
  • 31
  • Thank you @cafce25, this is exactly what I was looking for!!! Note that it works without the first `process(&mut *conn)` -- seems like I can just do the `process(conn)` twice. – Yuri Astrakhan Jun 03 '23 at 13:41
  • 2
    Ah yea with the function call it'll do the reborrow automatically because the argument to process is known to be an exclusive reference, when I tested it I inlined the `process` calls and then it's necessary to do the reborrow by hand. – cafce25 Jun 03 '23 at 13:47
  • Thanks, I actually did run into a situation where I had to reborrow by hand, and I'm not yet too clear on when I must do it by hand (any links on this?) – Yuri Astrakhan Jun 03 '23 at 15:29
  • 1
    Hmm there is quite a good explanation on here but I can't find it at the moment, but there is [this open issue on improving the documentation](https://github.com/rust-lang/reference/issues/788) which does have some details what works and what doesn't and several links to further resources. tl;dr when the compiler knows the target is a mutable reference it inserts a reborrow instead of moving the reference. – cafce25 Jun 03 '23 at 15:35