I want to implement transactions on rocket.rs dbpool

25 views Asked by At

I have a database connection using rocket-db-pools, using sqlx (pg), but I noticed that it doesn't implement a transaction feature. I tried to init a transaction using a guard:

#[derive(Database)]
#[database("db_name")]
pub struct Main(sqlx::PgPool);

pub struct PgTransaction{
    pub conn: sqlx::Transaction<'static, sqlx::Postgres>
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for PgTransaction {
    type Error = ();

    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, ()> {
        let db = request.guard::<&Main>().await;
        match db {
            rocket::outcome::Outcome::Success(main) => {
                match main.begin().await {
                    Ok(main) => {
                        Outcome::Success(
                            PgTransaction{
                                conn: main
                            }
                        )
                    }
                    Err(_) => Outcome::Error((Status::InternalServerError, ()))
                }
            },
            _ => Outcome::Error((Status::InternalServerError, ()))
        }
        
    }
}

It does indeed create a transaction, since every change don't go to the database until I commit it, but the is a problem: since PgTransaction is a guard, I don't see how to get the same connection on a fairing. When using request.guard::<&PgTransaction>(), it just requests a new guard, so even if I run commit on the result of this call, nothing will change:

#[rocket::async_trait]
impl Fairing for Transaction {
    fn info(&self) -> Info {
        Info {
            name: "Transaction manager",
            kind: Kind::Response
        }
    }

    async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) {
        match req.guard::<PgTransaction>().await {
            Outcome::Success(pgt) => {
                match res.status().class() {
                    StatusClass::Success | StatusClass::Redirection => {
                        println!("COMMIT");
                        pgt.conn.commit().await;
                    }

                    _ => {
                        println!("ROLLBACK");
                        pgt.conn.rollback().await;
                    }
                };
            },
            _ => ()
        };
    }
}

I can commit every time on every route I use it, but in every other api framework I can just use a middleware to guarantee that every transaction is commited/rollbacked depending on the route response. So how can I do it in Rocket.rs?

I also tried using request.local_cache(Ok(main)) on the guard, and request.local_cache(Err(())) to get the connection back, but .execute on sqlx don't accept a reference to a PgPool, PgConnection or any other execution, it needs ownership

0

There are 0 answers