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 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
119pub 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
136pub 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
162pub 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}