rusttransactionssqlx

Handle a function that can be called within a transaction or not


If a function can be called with a transaction or a direct connection (and so it should start a new transaction from there because it needs one) what do you suggest to use?

Example code:

async fn find_player_id_and_assign_goal(
    &self,
    db: Option<&mut sqlx::PgConnection>,
    id: &str,
    team_id: &str,
) -> Result<String, Error> {
    let mut tx = None;

    let db = match db {
        Some(db) => {
            // I need to detect if `db` here is already a transaction or not, in which case I should start a new one:
            db
        },
        None => &mut *tx.insert(self.pool.begin().await?),
    };

    let _ = self.team_service.find_team_id(Some(db), team_id).await?;

    let player_id = self.repo.find_player_id(Some(db), id).await?;

    self.goal_service.assign_to_player(Some(db), id).await?;

    if let Some(tx) = tx {
        tx.commit().await?;
    }

    Ok(player_id)
}

This can work, but I only have one issue:

If Some(db) => db I need to detect there if we're already in a transaction (if db is one). If not I should start a new one.

Do you think is there a better way to accomplish this?


Solution

  • If your inner calls to find_team_id, find_player_id, and assign_to_player need to be in a transaction together, then just make a transaction. Nested transactions are supported so it doesn't matter if db is already in a transaction or not.

    async fn find_player_id_and_assign_goal(
        &self,
        db: Option<&mut sqlx::PgConnection>,
        id: &str,
        team_id: &str,
    ) -> Result<String, Error> {
        let mut tx = match db {
            Some(db) => db.begin().await?,
            None => self.pool.begin().await?,
        };
    
        let _ = self.team_service.find_team_id(Some(&mut *tx), team_id).await?;
    
        let player_id = self.repo.find_player_id(Some(&mut *tx), id).await?;
    
        self.goal_service.assign_to_player(Some(&mut *tx), id).await?;
    
        tx.commit().await?;
    
        Ok(player_id)
    }