sqlx icon indicating copy to clipboard operation
sqlx copied to clipboard

implementation of `sqlx::Acquire` is not general enough

Open KaiserKarel opened this issue 4 years ago • 6 comments

I'm attempting to write and chain some functions which are generic over Transactions, Connections and pools by using Acquire:

pub async fn create_user<'a, 'c, C: sqlx::Acquire<'c, Database = sqlx::Postgres>>(
    conn: C,
    params: CreateUser<'a>,
) -> Result<(), CreateUserError> { ... }

pub async fn add_user_to_signup<'a, 'b, 'c, C: sqlx::Acquire<'c, Database = sqlx::Postgres>>(
    conn: C,
    params: AddUserToSignup<'a, 'b>,
) -> Result<(), AddUserToSignupError> { ... }

Works fine, but not when I am using them with a transaction:

db::create_user(&mut tx, CreateUser { id: &uuid }).await?;
db::add_user_to_signup(
    &mut tx,
    AddUserToSignup {
        user_id: &uuid,
        signup_id: &signup_id,
    },
)
.await?;
error: implementation of `sqlx::Acquire` is not general enough
  --> containers/api/src/service.rs:67:73
   |
67  |       ) -> Result<tonic::Response<FinalizeSignupResponse>, tonic::Status> {
   |  _________________________________________________________________________^
68  | |         let uuid = uuid::Uuid::new_v4();
69  | |         let request = &request.into_inner();
70  | |         let signup_id = uuid::Uuid::from_str(&request.signup_id)
...   |
107 | |         Ok(tonic::Response::new(FinalizeSignupResponse::default()))
108 | |     }
   | |_____^ implementation of `sqlx::Acquire` is not general enough
   | 
  ::: /home/karel/.cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-core-0.5.1/src/acquire.rs:8:1
   |
8   | / pub trait Acquire<'c> {
9   | |     type Database: Database;
10  | |
11  | |     type Connection: Deref<Target = <Self::Database as Database>::Connection> + DerefMut + Send;
...   |
15  | |     fn begin(self) -> BoxFuture<'c, Result<Transaction<'c, Self::Database>, Error>>;
16  | | }
   | |_- trait `sqlx::Acquire` defined here
   |
   = note: `sqlx::Acquire<'1>` would have to be implemented for the type `&'0 mut Transaction<'_, Postgres>`, for any two lifetimes `'0` and `'1`...
   = note: ...but `sqlx::Acquire<'2>` is actually implemented for the type `&'2 mut Transaction<'_, Postgres>`, for some specific lifetime `'2`

error: aborting due to previous error

How should one write functions generic over conns and transactions? I tried using a HKT bound for the `<c'> lifetime, which did not work alas.

KaiserKarel avatar Mar 29 '21 21:03 KaiserKarel

Not sure if there's a better way, but I use impl Executor<'_, Database = Postgres> a lot. You can't start a new transaction through Executor, but it works with &mut PgConnection, &mut Transaction and &PgPool.

NyxCode avatar Mar 29 '21 21:03 NyxCode

How do you then ensure that the executor is not moved (if you want to use it multiple times within the function, to execute multiple requests)?

KaiserKarel avatar Mar 29 '21 21:03 KaiserKarel

Right, that doesn't work. I use &mut Connection in these cases, that works with &mut Connection and &mut Transaction.

NyxCode avatar Mar 29 '21 22:03 NyxCode

Actually, this might be better from a design perspective too. If your (library) function is executing more than one query, it should use a transaction, since on error state changes cannot be rolled back by the caller.

KaiserKarel avatar Mar 29 '21 22:03 KaiserKarel

For anyone brave enough there is a generic way to pass in Executors and reuse them multiple times. The code can be found here: https://github.com/conblem/sqlx-abstract-executor . It involves a great deal of type magic, wouldn't recommend. The problem was still really intriguing and caused me a lot of headaches.

Your scientists were so preoccupied with whether or not they could, they didn’t stop to think if they should.

I'd be intrested to hear some feedback, it was a fun exercise!

conblem avatar Sep 30 '21 14:09 conblem

Some of these points are addressed in the documentation for https://docs.rs/sqlx/0.6.0/sqlx/trait.Acquire.html .

e00E avatar Jul 07 '22 10:07 e00E