diesel_crud/
lib.rs

1use diesel::{
2    backend::UsesAnsiSavepointSyntax,
3    insertable::CanInsertInSingleQuery,
4    query_builder::{InsertStatement, QueryFragment},
5    query_dsl::methods::ExecuteDsl,
6    r2d2::{ConnectionManager, PooledConnection},
7    Insertable, RunQueryDsl,
8};
9use dotenv::dotenv;
10use failure::Fail;
11use std::env;
12
13#[derive(Debug, Fail, derive_more::From)]
14pub enum Error {
15    #[fail(display = "database query error: {}", 0)]
16    Database(diesel::result::Error),
17    #[fail(display = "thread pool connection error: {}", 0)]
18    Connection(r2d2::Error),
19}
20
21type Pool<BaseConnection> = diesel::r2d2::Pool<ConnectionManager<BaseConnection>>;
22type Connection<BaseConnection> = PooledConnection<ConnectionManager<BaseConnection>>;
23
24pub trait CudQuery<BaseConnection>:
25    RunQueryDsl<Connection<BaseConnection>> + ExecuteDsl<Connection<BaseConnection>>
26where
27    BaseConnection: diesel::connection::Connection<
28            TransactionManager = diesel::connection::AnsiTransactionManager,
29        > + 'static,
30    BaseConnection::Backend: UsesAnsiSavepointSyntax,
31{
32}
33
34impl<T, BaseConnection> CudQuery<BaseConnection> for T
35where
36    T: RunQueryDsl<Connection<BaseConnection>> + ExecuteDsl<Connection<BaseConnection>>,
37    BaseConnection: diesel::connection::Connection<
38            TransactionManager = diesel::connection::AnsiTransactionManager,
39        > + 'static,
40    BaseConnection::Backend: UsesAnsiSavepointSyntax,
41{
42}
43
44pub trait LoadQuery<Item, BaseConnection>:
45    diesel::query_dsl::methods::LoadQuery<Connection<BaseConnection>, Item>
46where
47    BaseConnection: diesel::connection::Connection + 'static,
48{
49}
50
51impl<T, Item, BaseConnection> LoadQuery<Item, BaseConnection> for T
52where
53    T: diesel::query_dsl::methods::LoadQuery<Connection<BaseConnection>, Item>,
54    BaseConnection: diesel::connection::Connection<
55            TransactionManager = diesel::connection::AnsiTransactionManager,
56        > + 'static,
57    BaseConnection::Backend: UsesAnsiSavepointSyntax,
58{
59}
60
61pub struct Db<BaseConnection: diesel::connection::Connection + 'static> {
62    pool: Pool<BaseConnection>,
63}
64
65impl<BaseConnection> Db<BaseConnection>
66where
67    BaseConnection: diesel::connection::Connection + 'static,
68{
69    /// Creates a database connection pool.
70    pub fn new() -> Result<Self, Error> {
71        dotenv().ok();
72        let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
73        Ok(Self {
74            pool: Pool::<BaseConnection>::builder().build(ConnectionManager::new(&database_url))?,
75        })
76    }
77
78    pub fn conn(&self) -> Result<Connection<BaseConnection>, Error> {
79        Ok(self.pool.get()?)
80    }
81}
82
83impl<BaseConnection> Db<BaseConnection>
84where
85    BaseConnection: diesel::connection::Connection<
86        TransactionManager = diesel::connection::AnsiTransactionManager,
87    >,
88    BaseConnection::Backend: UsesAnsiSavepointSyntax,
89{
90    pub fn cud<C>(&self, cud: C) -> Result<(), Error>
91    where
92        C: Cud<BaseConnection>,
93        C::Query: QueryFragment<BaseConnection::Backend> + diesel::query_builder::QueryId,
94    {
95        cud.execute(self)
96    }
97
98    pub fn cud_query(&self, query: impl CudQuery<BaseConnection>) -> Result<(), Error> {
99        Ok(query.execute(&self.conn()?).map(|_| ())?)
100    }
101}
102
103impl<BaseConnection> Db<BaseConnection>
104where
105    BaseConnection: diesel::connection::Connection,
106{
107    pub fn load<L>(&self, load: L) -> Result<Vec<L::Item>, Error>
108    where
109        L: Load<BaseConnection>,
110    {
111        load.load(self)
112    }
113
114    pub fn load_query<T>(&self, query: impl LoadQuery<T, BaseConnection>) -> Result<Vec<T>, Error> {
115        Ok(query.load::<T>(&self.conn()?)?)
116    }
117}
118
119/// Trait which is implemented by create, update, and delete operations.
120pub trait Cud<BaseConnection>: Sized
121where
122    BaseConnection: diesel::connection::Connection<
123            TransactionManager = diesel::connection::AnsiTransactionManager,
124        > + 'static,
125    BaseConnection::Backend: UsesAnsiSavepointSyntax,
126{
127    type Query: CudQuery<BaseConnection>;
128
129    fn execute(self, db: &Db<BaseConnection>) -> Result<(), Error> {
130        db.cud_query(self.query())
131    }
132
133    fn query(self) -> Self::Query;
134}
135
136/// Trait for update operations which auto-implements [`Cud`].
137pub trait Create: Sized {
138    type Table: diesel::Table;
139
140    fn table() -> Self::Table;
141}
142
143impl<T, Table, BaseConnection> Cud<BaseConnection> for T
144where
145    T: Create<Table = Table> + Insertable<Table>,
146    Table: diesel::Table,
147    T::Values: CanInsertInSingleQuery<BaseConnection::Backend>,
148    T::Values: QueryFragment<BaseConnection::Backend>,
149    Table::FromClause: QueryFragment<BaseConnection::Backend>,
150    BaseConnection: diesel::connection::Connection<
151            TransactionManager = diesel::connection::AnsiTransactionManager,
152        > + 'static,
153    BaseConnection::Backend: UsesAnsiSavepointSyntax,
154{
155    type Query = InsertStatement<Table, <Self as Insertable<Table>>::Values>;
156
157    fn query(self) -> Self::Query {
158        diesel::insert_into(T::table()).values(self)
159    }
160}
161
162/// Trait which is implemented by read operations.
163pub trait Load<BaseConnection>: Sized
164where
165    BaseConnection: diesel::connection::Connection + 'static,
166{
167    type Item;
168    type Query: LoadQuery<Self::Item, BaseConnection>;
169
170    fn load(self, db: &Db<BaseConnection>) -> Result<Vec<Self::Item>, Error> {
171        db.load_query(self.query())
172    }
173
174    fn query(self) -> Self::Query;
175}