0

I am using Rust with sqlx and postgres to build a REST API. I am trying to build a Database struct which has a generic connection param in it.

struct Database<T>
where
    T: Sync + Send,
    for<'a> &'a T: sqlx::Executor<'a, Database = Postgres>,
{
  conn: T
}

T would be owned by the struct, and &T is expected to implement Executor trait.

I am able to use Pool<Postgres> as T, since &Pool<Postgres> implements the Executor.

What I want is (and this is the reason I made the conn a generic type) to be able to use Transaction<Postgres> as T. But the problem is that &Transaction<Postgres> does not implement the Executor trait, but &mut Transaction<Postgres> does.

The reason I want to do this is there are CRUD functions that I want to be able to use with both a transaction and a pool connection. And I don't want to write duplicate code. How can I achieve this?

1 Answers1

0

Maybe someone has a better idea, but it seems the following could work.

use std::marker::PhantomData;
use sqlx::{Pool, Transaction, Postgres};

trait Connection<'a>: Sync + Send {
    type Executor: sqlx::Executor<'a>;
    fn executor(&'a mut self) -> Self::Executor;
}

impl <'a> Connection<'a> for Pool<Postgres> {
    type Executor = &'a Self;
    fn executor(&'a mut self) -> Self::Executor {
        &*self
    }
}

impl <'c> Connection<'c> for Transaction<'c, Postgres> {
    type Executor = &'c mut Self;
    fn executor(&'c mut self) -> Self::Executor {
        self
    }
}

struct Database<'a, T: Connection<'a>>
{
    conn: T,
    phantom: PhantomData<&'a T>
}

impl <'a, T: Connection<'a>> Database<'a, T> {
    fn q(&'a mut self) {
        let _: T::Executor = self.conn.executor();
        // sqlx::query!("Some query").execute(self.conn.executor())
    }
}

At the very least it compiles :)

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • This is great! This works with no problem for impl blocks `impl Database<'static, Pool>` and `impl<'a> Database<'a, Transaction<'a, Postgres>>`. But when I try to implement functionality that are common to both pool and transaction it does not work. I tried this: `impl<'a, T> PostgresDb<'a, T> where T: Connection<'a>`. When I do `query!("Some query").execute(&self.conn)`, it say Executor not implemented for T. – Ahmet Yazıcı Apr 10 '23 at 15:09
  • You'll need to add a method to the trait returning a `Self::Executor` and use it instead of `&` in that code. If you figure it out, add a comment; otherwise I can try tomorrow. – Alexey Romanov Apr 11 '23 at 00:38
  • Or maybe this doesn't work, because the method would need to take `&Self` for `Pool` and `&mut Self` for `Transaction`. You could also have a method taking `Self` which would work for both but only get you a single query :( – Alexey Romanov Apr 11 '23 at 07:57
  • 1
    @AhmetYazıcı Maybe you don't need `T` to be owned by the `Database` structure, and it can just contain the correct kind of reference from the beginning? – Alexey Romanov Apr 11 '23 at 08:07
  • The last comment of yours seems to be the only way to do it. Passing directly `&Pool` or `&mut Transaction` to create the Database instance. This way T will be required to implement the `Executor` trait directly. The slight problem of this is that, Database with pool connection can not directly create a transaction from its member function. When I wanna use a transaction connection in a function, I have to get the pool connection within the business logic, create a transaction, and create a new instance of Database with that transaction connection. But that's a necessary overhead I think. – Ahmet Yazıcı Apr 11 '23 at 08:18
  • Actually, see a new simpler attempt. Can't test it fully because `query!` won't compile on Rust Explorer, but I'd expect it can be made to work. – Alexey Romanov Apr 11 '23 at 10:26