How to wrap Sqlx data access types retaining transaction support?

Viewed 139

I need to wrap Sqlx connection and Transaction types into my corresponding opaque types that will implement domain-specific data access methods. Trying to achieve that I'm struggling to support spawning of transactions. Here is my simplified code:

use sqlx::{
    Connection, Database, Sqlite, SqliteConnection as SqlxSqliteConnection,
    Transaction as SqlxTransaction,
};

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("unknown error")]
    Unknown,
}

pub type Result<T> = std::result::Result<T, Error>;

pub type Future<'a, T> = Pin<Box<dyn std::future::Future<Output = T> + Send + 'a>>;

pub struct Transaction<'c, DB: Database>(SqlxTransaction<'c, DB>);

pub trait DataConn<'c, DB: Database>
where
    &'c mut Transaction<'c, DB>: DataTx<'c, DB>,
{
    fn begin(self) -> Future<'c, Result<Transaction<'c, DB>>>;
}

pub trait DataTx<'c, DB: Database>
where
    &'c mut Transaction<'c, DB>: DataTx<'c, DB>,
{
    fn begin(self) -> Future<'c, Result<Transaction<'c, DB>>>;
}

pub struct SqliteConnection(SqlxSqliteConnection);

impl<'c> DataConn<'c, Sqlite> for &'c mut SqliteConnection {
    fn begin(self) -> Future<'c, Result<Transaction<'c, Sqlite>>> {
        Box::pin(async move {
            Ok(Transaction(Connection::begin(&mut self.0).await?))
        })
    }
}

impl<'c> DataTx<'c, Sqlite> for &'c mut Transaction<'c, Sqlite> {
    fn begin(self) -> Future<'c, Result<Transaction<'c, Sqlite>>> {
        Box::pin(async move { Ok(Transaction(self.0.begin().await?)) })
    }
}

#[cfg(test)]
mod test {
    use super::*;

    async fn test_transactions<'c, DB, Conn>(conn: Conn)
    where
        DB: Database,
        Conn: DataConn<'c, DB>,
        &'c mut Transaction<'c, DB>: DataTx<'c, DB>,
    {
        let mut tx = conn.begin().await.unwrap();
        let tx2 = tx.begin().await.unwrap(); // fails here
    }
}

As a result I get the following error details:

error[E0597]: `tx` does not live long enough
  --> src/data2/mod.rs:54:19
   |
47 |     async fn test_transactions<'c, DB, Conn>(conn: Conn)
   |                                -- lifetime `'c` defined here
...
54 |         let tx2 = tx.begin().await.unwrap();
   |                   ^^^^^^^^^^
   |                   |
   |                   borrowed value does not live long enough
   |                   argument requires that `tx` is borrowed for `'c`
...
57 |     }
   |     - `tx` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.

Can you explain why tx2 does outlive tx in the test function below and how I can fix that?

0 Answers
Related