Actix + Diesel - using a transaction


#1

Hi all,

Getting back into Rust after about a year. Trying to work with the actix-web and diesel libraries. Following along with the example here: https://github.com/actix/examples/blob/master/diesel/src/db.rs

However I’m running into the following error:

error: the `transaction` method cannot be invoked on a trait object
  --> src/db.rs:49:11
   |
49 |     *conn.transaction::<_, actix_web::error::ResponseError, _>(|| {
   |           ^^^^^^^^^^^
   |
   = note: another candidate was found in the following trait, perhaps add a `use` for it:
           `use diesel::Connection;`

What is the proper way to deref the conn so that I can call the transaction function?

Here is the code file which is causing the issue:


use actix::prelude::*;
use actix_web::*;
use diesel;
use diesel::prelude::*;
use diesel::r2d2::{ConnectionManager, Pool};

use models;
use schema;

/**
 * This is the DB executor actor. We are going to run 3 of them in parallel.
 */
pub struct DbExecutor(pub Pool<ConnectionManager<MysqlConnection>>);

pub struct CreateMessageIntent {
    pub batch_id: String,
    pub message_type_id: u16,
    pub xref: String,
    pub pid: String,
    pub body: Vec<u8>,
}

impl Message for CreateMessageIntent {
    type Result = Result<models::SendMessageIntent, Error>;
}

impl Actor for DbExecutor {
    type Context = SyncContext<Self>;
}

impl Handler<CreateMessageIntent> for DbExecutor {
    type Result = Result<models::SendMessageIntent, Error>;

    fn handle(&mut self, msg: CreateMessageIntent, _: &mut Self::Context) -> Self::Result {
    use self::schema::send_message_intent::dsl::*;

    let new_intent = models::NewSendMessageIntent {
        batch_id: &msg.batch_id,
        message_type_id: msg.message_type_id,
        xref: &msg.xref,
        pid: &msg.pid,
        body: &msg.body,
    };

    let conn: &MysqlConnection = &self.0.get().unwrap();

    *conn.transaction::<_, actix_web::error::ResponseError, _>(|| {
        diesel::insert_into(send_message_intent)
            .values(new_intent)
            .execute(conn)
            .map_err(|_| {
                error::ErrorInternalServerError(
                    "Error inserting send message intent"
                )
            })?;

        send_message_intent
            .order(id.desc())
            .first(conn)
            .map_err(|_| {
                error::ErrorInternalServerError(
                    "Error loading new send message intent"
                )
            })?
    }).map_err(|_| {
        error::ErrorInternalServerError(
            "Error inserting send message intent"
        )
    })?
    }
}


#2

Is there a better place to ask this? Or, is the question not framed properly?


#3

This is the proper forum in which to ask, and the question is framed quite well. Give people a day to answer. Some of us are in later time zones than you apparently are, or may have other more critical work to do first.


#4

Thank you for the response. Just wanted to make sure I had everything in order. I did not intend for my comment to seem like I’m trying to rush along the process; I apologize if that tone was present.


#5

Here’s an example of a transaction I was able to use successfully with actix and diesel

impl actix::Handler<CreateUser> for DbExecutor {
type Result = Result<models::User, actix_web::Error>;

fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result {
    let new_user = models::NewUser {
        email: &msg.email,
    };

    let conn: &diesel::sqlite::SqliteConnection = &self.0.get().unwrap();

    Ok(conn.transaction(|| {
        diesel::insert_into(schema::users::dsl::users)
            .values(&new_user)
            .execute(conn)
            // Previously was trying to use last_insert_rowid() raw sql but that didn't work
            .and_then(|_| {
                schema::users::dsl::users.order(schema::users::id.desc()).first(conn)
            })

    }).map_err(|_| actix_web::error::ErrorInternalServerError("Error retrieving inserted user"))?)
}

}


#6

Interesting, I wonder if the specification of diesel::sqlite:: is the difference. I will try when I return home tonight. Thank you :pray:


#7

There shouldn’t be a difference, I’m not sure why you want to dereference when the examples in the docs don’t do that either? https://docs.diesel.rs/diesel/connection/trait.Connection.html#method.transaction


#8

I tried without the deref and received the same error.

I see a couple other possibilities:

  1. You don’t declare the <_, actix_web::error::ResponseError, _> generic
  2. Your transaction response is wrapped in an OK()

Wish I could get to this and try it out. :slight_smile:


#9

The following code worked… I believe it was a combination of an unnecessary deref (*) and bad use of .map_err.

let new_intent = models::NewSendMessageIntent {
    batch_id: &msg.batch_id,
    message_type_id: msg.message_type_id,
    xref: &msg.xref,
    pid: &msg.pid,
};

let conn: &MysqlConnection = &self.0.get().unwrap();

conn.transaction(|| {
    use self::schema::send_message_intent::dsl::*;

    diesel::insert_into(send_message_intent)
        .values(new_intent)
        .execute(conn)?;

    send_message_intent
        .order(id.desc())
        .first::<self::models::SendMessageIntent>(conn)
}).map_err(|_| {
    error::ErrorInternalServerError(
        "Error inserting send message intent."
    )
})

#10

Thank you @zinclozenge for the help