Francois Gosselin
Francois Gosselin

Reputation: 163

Transactions in Rust Diesel

I'm learning Rust and the Rocket framework and am trying to build an application with database connection using Diesel. I've been able to successfully connect to a SqLite db and perform some basic CRUD operations. Now I want to be able to ensure transaction atomicity. For example, I have a user model with a user table and a role table that are in a many-to-many relationship and are joined through a junction table called user_role. So to insert a new user, I do something like this:

    let new_user = CreateUser{id: Option::from(new_id), username, email, password_hash: password };
    diesel::insert_into(user::table)
        .values(&new_user)
        .execute(connection)
        .expect("Error saving the user");

    let user_role = CreateUserRole{user_id: new_user.id, role_id: existing_role.id};
    diesel::insert_into(user_role::table)
        .values(&user_role)
        .execute(connection)
        .expect("Could not add role to user");

Now the thing is, if there is a problem with the role assignation, the user gets created, but no role is assigned which I don't want to happen. Is there a way to ensure atomicity so that either both operations get performed or none of them. I saw this page in the doc about transaction manager: https://docs.diesel.rs/master/diesel/connection/trait.TransactionManager.html, but I don't really understand how I could use that and I haven't found any example.

Upvotes: 6

Views: 4819

Answers (1)

weiznich
weiznich

Reputation: 3435

TransactionManager is usually the wrong abstraction for building transactions. It is only useful in specific cases, where you want strict control over the transaction. Usually you want to use Connection::transaction to wrap your operations into a transaction. Anything inside the provided closure is executed in a transaction. The transaction is committed if Ok is returned. It will be rolled back if any error is returned. Given your example that would look like

connection.transaction(|connection| {
    let new_user = CreateUser{id: Option::from(new_id), username, email, password_hash: password };
    diesel::insert_into(user::table)
        .values(&new_user)
        .execute(connection)?;

    let user_role = CreateUserRole{user_id: new_user.id, role_id: existing_role.id};
    diesel::insert_into(user_role::table)
        .values(&user_role)
        .execute(connection)?;

    // That uses `diesel::result::Error` as error type
    // you can use any other error type here as long as
    // it implements `From<diesel::result::Error>`.
    diesel::result::QueryResult::Ok(())
})

Upvotes: 10

Related Questions