Skip to main content

actix_diesel_actor/
lib.rs

1use ::actix::prelude::*;
2use diesel::pg::Pg;
3use diesel::prelude::*;
4use diesel::query_builder::{QueryFragment, QueryId, SelectQuery};
5use diesel::query_dsl::{LoadQuery, RunQueryDsl};
6use diesel::r2d2;
7use diesel::r2d2::{ConnectionManager, Pool};
8use diesel::result::Error;
9use dotenv::dotenv;
10use std::env;
11use std::marker::PhantomData;
12
13use log::debug;
14
15pub struct DbExecutor<T: 'static>
16where
17    T: Connection,
18{
19    pub pool: Pool<ConnectionManager<T>>,
20}
21impl<T: Connection> Actor for DbExecutor<T> {
22    type Context = SyncContext<Self>;
23
24    fn started(&mut self, _ctx: &mut Self::Context) {
25        debug!("I am alive!");
26    }
27}
28
29/// let query = users.filter(group_id.eq(group.id));
30/// let query = query.order((lname.asc(), fname.asc()));
31/// let select = SQuery {
32///     select: query,
33///     phantom: PhantomData::<Users>,
34/// };
35/// let res = req.state().rdb.send(select).wait().ok().unwrap();
36pub struct SQuery<S, I> {
37    pub select: S,
38    pub phantom: PhantomData<I>,
39}
40impl<S, I: 'static> Message for SQuery<S, I> {
41    type Result = Result<Vec<I>, Error>;
42}
43impl<S: LoadQuery<PgConnection, I> + SelectQuery + QueryFragment<Pg>, I: 'static>
44    Handler<SQuery<S, I>> for DbExecutor<PgConnection>
45{
46    type Result = Result<Vec<I>, Error>;
47
48    fn handle(&mut self, msg: SQuery<S, I>, _: &mut Self::Context) -> Self::Result {
49        let dbg = diesel::debug_query(&msg.select);
50        debug!("{:?}", dbg);
51        let pool = &self.pool;
52        if let Ok(conn) = pool.get() {
53            let res = msg.select.load::<I>(&conn);
54            return res;
55        }
56        Ok(Vec::new())
57    }
58}
59
60// let target = users.filter(id.eq(uid));
61// let query = diesel::update(target).set((
62//     fname.eq(form.fname),
63//     lname.eq(form.lname),
64//     email.eq(form.email),
65//     phone.eq(form.phone),
66//     comment.eq(form.comment),
67// ));
68// let upd = WQuery {
69//     query,
70//     phantom: PhantomData::<Users>,
71// };
72// let res = req.state().wdb.send(upd).wait().ok().unwrap();
73pub struct WQuery<W, I> {
74    pub query: W,
75    pub phantom: PhantomData<I>,
76}
77impl<W, I: 'static> Message for WQuery<W, I> {
78    type Result = Result<Vec<I>, Error>;
79}
80impl<W: LoadQuery<PgConnection, I> + QueryFragment<Pg>, I: 'static> Handler<WQuery<W, I>>
81    for DbExecutor<PgConnection>
82{
83    type Result = Result<Vec<I>, Error>;
84
85    fn handle(&mut self, msg: WQuery<W, I>, _: &mut Self::Context) -> Self::Result {
86        let dbg = diesel::debug_query(&msg.query);
87        debug!("{:?}", dbg);
88        let pool = &self.pool;
89        if let Ok(conn) = pool.get() {
90            let res = msg.query.get_results::<I>(&conn);
91            return res;
92        }
93        Ok(Vec::new())
94    }
95}
96
97pub struct AppState {
98    pub rdb: Addr<DbExecutor<diesel::PgConnection>>,
99    pub wdb: Addr<DbExecutor<diesel::PgConnection>>,
100}
101
102/// Use the Read setting with a connection String to access a read-only db replica
103/// Use the Write setting to udpate with a connection String to a writeable DB
104/// There is a distinct r2d2 pool for each thread the DbExecutor actor runs on.
105/// Therefore the pool is configured with max_size(3) and min_idle(Some(0)):
106/// It creates a maximum of 3 connection per pool, starting with 0.
107pub enum ConnectionType {
108    Read,
109    Write,
110}
111pub fn db_setup(conn_type: ConnectionType) -> actix::Addr<DbExecutor<diesel::PgConnection>> {
112    dotenv().ok();
113
114    let var = match conn_type {
115        ConnectionType::Read => "DB_READ_URL",
116        ConnectionType::Write => "DB_WRITE_URL",
117    };
118    let database_url = env::var(var).unwrap_or_else(|_| panic!("{} must be set", var));
119    let manager = ConnectionManager::<PgConnection>::new(database_url);
120    let pool = r2d2::Pool::builder()
121        .max_size(3)
122        .min_idle(Some(0))
123        .build(manager)
124        .expect("Failed to create pool.");
125    SyncArbiter::start(3, move || DbExecutor { pool: pool.clone() })
126}
127
128#[derive(Debug)]
129pub enum DbExecutorError {
130    DatabaseError(diesel::result::Error),
131    MailBoxError(actix::MailboxError),
132    Unknown,
133}
134impl From<diesel::result::Error> for DbExecutorError {
135    fn from(error: diesel::result::Error) -> Self {
136        DbExecutorError::DatabaseError(error)
137    }
138}
139impl From<actix::MailboxError> for DbExecutorError {
140    fn from(error: actix::MailboxError) -> Self {
141        DbExecutorError::MailBoxError(error)
142    }
143}
144
145/// This message can be used to ask for an r2d2 pg connection.
146/// Generally, try to avoid it.
147/// let conn = req.state().wdb.send(Conn{}).wait().ok().unwrap().unwrap();
148/// let res= diesel::delete(users.filter(id.eq(uid))).execute(&conn);
149pub struct Conn;
150impl Message for Conn {
151    type Result = Result<r2d2::PooledConnection<ConnectionManager<PgConnection>>, Error>;
152}
153impl Handler<Conn> for DbExecutor<PgConnection> {
154    type Result = Result<r2d2::PooledConnection<ConnectionManager<PgConnection>>, Error>;
155
156    fn handle(&mut self, _msg: Conn, _: &mut Self::Context) -> Self::Result {
157        // fn handle(&mut self, msg: Conn, _: &mut Context<Self>) -> Self::Result {
158        let pool = &self.pool;
159        if let Ok(conn) = pool.get() {
160            return Ok(conn);
161        }
162        Err(Error::NotFound)
163    }
164}
165
166/// Devised to be used for deleting things, but allows other RunQueryDsl queries as well.
167/// let query =  diesel::delete(users.filter(id.eq(uid)));
168/// let del = DQuery {
169///     query
170/// };
171/// let res = req.state().wdb.send(del).wait().ok().unwrap();
172pub struct DQuery<D> {
173    pub query: D,
174}
175impl<D> Message for DQuery<D> {
176    type Result = Result<usize, Error>;
177}
178impl<D: RunQueryDsl<PgConnection> + QueryId + QueryFragment<Pg>> Handler<DQuery<D>>
179    for DbExecutor<PgConnection>
180{
181    type Result = Result<usize, Error>;
182
183    fn handle(&mut self, msg: DQuery<D>, _: &mut Self::Context) -> Self::Result {
184        let dbg = diesel::debug_query(&msg.query);
185        debug!("{:?}", dbg);
186        let pool = &self.pool;
187        if let Ok(conn) = pool.get() {
188            let res = msg.query.execute(&conn);
189            return res;
190        }
191        Err(Error::NotFound)
192    }
193}