db.rs

  1pub mod kvp;
  2
  3// Re-export
  4pub use anyhow;
  5pub use indoc::indoc;
  6pub use lazy_static;
  7pub use sqlez;
  8pub use sqlez_macros;
  9
 10use sqlez::domain::Migrator;
 11use sqlez::thread_safe_connection::ThreadSafeConnection;
 12use std::fs::{create_dir_all, remove_dir_all};
 13use std::path::Path;
 14use std::sync::atomic::{AtomicBool, Ordering};
 15use util::channel::{ReleaseChannel, RELEASE_CHANNEL, RELEASE_CHANNEL_NAME};
 16use util::paths::DB_DIR;
 17
 18const INITIALIZE_QUERY: &'static str = indoc! {"
 19    PRAGMA journal_mode=WAL;
 20    PRAGMA synchronous=NORMAL;
 21    PRAGMA busy_timeout=1;
 22    PRAGMA foreign_keys=TRUE;
 23    PRAGMA case_sensitive_like=TRUE;
 24"};
 25
 26lazy_static::lazy_static! {
 27    static ref DB_WIPED: AtomicBool = AtomicBool::new(false);
 28}
 29
 30/// Open or create a database at the given directory path.
 31pub fn open_file_db<M: Migrator>() -> ThreadSafeConnection<M> {
 32    // Use 0 for now. Will implement incrementing and clearing of old db files soon TM
 33    let current_db_dir = (*DB_DIR).join(Path::new(&format!("0-{}", *RELEASE_CHANNEL_NAME)));
 34
 35    if *RELEASE_CHANNEL == ReleaseChannel::Dev
 36        && std::env::var("WIPE_DB").is_ok()
 37        && !DB_WIPED.load(Ordering::Acquire)
 38    {
 39        remove_dir_all(&current_db_dir).ok();
 40        DB_WIPED.store(true, Ordering::Relaxed);
 41    }
 42
 43    create_dir_all(&current_db_dir).expect("Should be able to create the database directory");
 44    let db_path = current_db_dir.join(Path::new("db.sqlite"));
 45
 46    ThreadSafeConnection::new(db_path.to_string_lossy().as_ref(), true)
 47        .with_initialize_query(INITIALIZE_QUERY)
 48}
 49
 50pub fn open_memory_db<M: Migrator>(db_name: &str) -> ThreadSafeConnection<M> {
 51    ThreadSafeConnection::new(db_name, false).with_initialize_query(INITIALIZE_QUERY)
 52}
 53
 54/// Implements a basic DB wrapper for a given domain
 55#[macro_export]
 56macro_rules! connection {
 57    ($id:ident: $t:ident<$d:ty>) => {
 58        pub struct $t(::db::sqlez::thread_safe_connection::ThreadSafeConnection<$d>);
 59
 60        impl ::std::ops::Deref for $t {
 61            type Target = ::db::sqlez::thread_safe_connection::ThreadSafeConnection<$d>;
 62
 63            fn deref(&self) -> &Self::Target {
 64                &self.0
 65            }
 66        }
 67
 68        ::db::lazy_static::lazy_static! {
 69            pub static ref $id: $t = $t(if cfg!(any(test, feature = "test-support")) {
 70                ::db::open_memory_db(stringify!($id))
 71            } else {
 72                ::db::open_file_db()
 73            });
 74        }
 75    };
 76}
 77
 78#[macro_export]
 79macro_rules! query {
 80    ($vis:vis fn $id:ident() -> Result<()> { $($sql:tt)+ }) => {
 81        $vis fn $id(&self) -> $crate::anyhow::Result<()> {
 82            use $crate::anyhow::Context;
 83
 84            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
 85
 86            self.exec(sql_stmt)?().context(::std::format!(
 87                "Error in {}, exec failed to execute or parse for: {}",
 88                ::std::stringify!($id),
 89                sql_stmt,
 90            ))
 91        }
 92    };
 93    ($vis:vis async fn $id:ident() -> Result<()> { $($sql:tt)+ }) => {
 94        $vis async fn $id(&self) -> $crate::anyhow::Result<()> {
 95            use $crate::anyhow::Context;
 96
 97
 98            self.write(|connection| {
 99                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
100
101                connection.exec(sql_stmt)?().context(::std::format!(
102                    "Error in {}, exec failed to execute or parse for: {}",
103                    ::std::stringify!($id),
104                    sql_stmt
105                ))
106            }).await
107        }
108    };
109    ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<()> { $($sql:tt)+ }) => {
110        $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> {
111            use $crate::anyhow::Context;
112
113            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
114
115            self.exec_bound::<($($arg_type),+)>(sql_stmt)?(($($arg),+))
116                .context(::std::format!(
117                    "Error in {}, exec_bound failed to execute or parse for: {}",
118                    ::std::stringify!($id),
119                    sql_stmt
120                ))
121        }
122    };
123    ($vis:vis async fn $id:ident($arg:ident: $arg_type:ty) -> Result<()> { $($sql:tt)+ }) => {
124        $vis async fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<()> {
125            use $crate::anyhow::Context;
126
127
128            self.write(move |connection| {
129                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
130
131                connection.exec_bound::<$arg_type>(sql_stmt)?($arg)
132                    .context(::std::format!(
133                        "Error in {}, exec_bound failed to execute or parse for: {}",
134                        ::std::stringify!($id),
135                        sql_stmt
136                    ))
137            }).await
138        }
139    };
140    ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<()> { $($sql:tt)+ }) => {
141        $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> {
142            use $crate::anyhow::Context;
143
144            self.write(move |connection| {
145                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
146
147                connection.exec_bound::<($($arg_type),+)>(sql_stmt)?(($($arg),+))
148                    .context(::std::format!(
149                        "Error in {}, exec_bound failed to execute or parse for: {}",
150                        ::std::stringify!($id),
151                        sql_stmt
152                    ))
153            }).await
154        }
155    };
156    ($vis:vis fn $id:ident() ->  Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
157         $vis fn $id(&self) -> $crate::anyhow::Result<Vec<$return_type>> {
158             use $crate::anyhow::Context;
159
160             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
161
162             self.select::<$return_type>(sql_stmt)?(())
163                 .context(::std::format!(
164                     "Error in {}, select_row failed to execute or parse for: {}",
165                     ::std::stringify!($id),
166                     sql_stmt
167                 ))
168         }
169    };
170    ($vis:vis async fn $id:ident() ->  Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
171        pub async fn $id(&self) -> $crate::anyhow::Result<Vec<$return_type>> {
172            use $crate::anyhow::Context;
173
174            self.write(|connection| {
175                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
176
177                connection.select::<$return_type>(sql_stmt)?(())
178                    .context(::std::format!(
179                        "Error in {}, select_row failed to execute or parse for: {}",
180                        ::std::stringify!($id),
181                        sql_stmt
182                    ))
183            }).await
184        }
185    };
186    ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
187         $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Vec<$return_type>> {
188             use $crate::anyhow::Context;
189
190             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
191
192             self.select_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
193                 .context(::std::format!(
194                     "Error in {}, exec_bound failed to execute or parse for: {}",
195                     ::std::stringify!($id),
196                     sql_stmt
197                 ))
198         }
199    };
200    ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
201        $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Vec<$return_type>> {
202            use $crate::anyhow::Context;
203
204            self.write(|connection| {
205                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
206
207                connection.select_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
208                    .context(::std::format!(
209                        "Error in {}, exec_bound failed to execute or parse for: {}",
210                        ::std::stringify!($id),
211                        sql_stmt
212                    ))
213            }).await
214        }
215    };
216    ($vis:vis fn $id:ident() ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
217         $vis fn $id(&self) -> $crate::anyhow::Result<Option<$return_type>> {
218             use $crate::anyhow::Context;
219
220             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
221
222             self.select_row::<$return_type>(sql_stmt)?()
223                 .context(::std::format!(
224                     "Error in {}, select_row failed to execute or parse for: {}",
225                     ::std::stringify!($id),
226                     sql_stmt
227                 ))
228         }
229    };
230    ($vis:vis async fn $id:ident() ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
231        $vis async fn $id(&self) -> $crate::anyhow::Result<Option<$return_type>> {
232            use $crate::anyhow::Context;
233
234            self.write(|connection| {
235                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
236
237                connection.select_row::<$return_type>(sql_stmt)?()
238                    .context(::std::format!(
239                        "Error in {}, select_row failed to execute or parse for: {}",
240                        ::std::stringify!($id),
241                        sql_stmt
242                    ))
243            }).await
244        }
245    };
246    ($vis:vis fn $id:ident($arg:ident: $arg_type:ty) ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
247        $vis fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<Option<$return_type>>  {
248            use $crate::anyhow::Context;
249
250            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
251
252            self.select_row_bound::<$arg_type, $return_type>(sql_stmt)?($arg)
253                .context(::std::format!(
254                    "Error in {}, select_row_bound failed to execute or parse for: {}",
255                    ::std::stringify!($id),
256                    sql_stmt
257                ))
258
259        }
260    };
261    ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
262         $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Option<$return_type>>  {
263             use $crate::anyhow::Context;
264
265             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
266
267             self.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
268                 .context(::std::format!(
269                     "Error in {}, select_row_bound failed to execute or parse for: {}",
270                     ::std::stringify!($id),
271                     sql_stmt
272                 ))
273
274         }
275    };
276    ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
277        $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Option<$return_type>>  {
278            use $crate::anyhow::Context;
279
280
281            self.write(|connection| {
282                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
283
284                connection.select_row_bound::<($($arg_type),+), $return_type>(indoc! { $sql })?(($($arg),+))
285                    .context(::std::format!(
286                        "Error in {}, select_row_bound failed to execute or parse for: {}",
287                        ::std::stringify!($id),
288                        sql_stmt
289                    ))
290            }).await
291        }
292    };
293    ($vis:vis fn $id:ident() ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
294         $vis fn $id(&self) ->  $crate::anyhow::Result<$return_type>  {
295             use $crate::anyhow::Context;
296
297             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
298
299             self.select_row::<$return_type>(indoc! { $sql })?()
300                 .context(::std::format!(
301                     "Error in {}, select_row_bound failed to execute or parse for: {}",
302                     ::std::stringify!($id),
303                     sql_stmt
304                 ))?
305                 .context(::std::format!(
306                     "Error in {}, select_row_bound expected single row result but found none for: {}",
307                     ::std::stringify!($id),
308                     sql_stmt
309                 ))
310         }
311    };
312    ($vis:vis async fn $id:ident() ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
313        $vis async fn $id(&self) ->  $crate::anyhow::Result<$return_type>  {
314            use $crate::anyhow::Context;
315
316            self.write(|connection| {
317                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
318
319                connection.select_row::<$return_type>(sql_stmt)?()
320                    .context(::std::format!(
321                        "Error in {}, select_row_bound failed to execute or parse for: {}",
322                        ::std::stringify!($id),
323                        sql_stmt
324                    ))?
325                    .context(::std::format!(
326                        "Error in {}, select_row_bound expected single row result but found none for: {}",
327                        ::std::stringify!($id),
328                        sql_stmt
329                    ))
330            }).await
331        }
332    };
333    ($vis:vis fn $id:ident($arg:ident: $arg_type:ty) ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
334        pub fn $id(&self, $arg: $arg_type) ->  $crate::anyhow::Result<$return_type>  {
335            use $crate::anyhow::Context;
336
337            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
338
339            self.select_row_bound::<$arg_type, $return_type>(sql_stmt)?($arg)
340                .context(::std::format!(
341                    "Error in {}, select_row_bound failed to execute or parse for: {}",
342                    ::std::stringify!($id),
343                    sql_stmt
344                ))?
345                .context(::std::format!(
346                    "Error in {}, select_row_bound expected single row result but found none for: {}",
347                    ::std::stringify!($id),
348                    sql_stmt
349                ))
350        }
351    };
352    ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
353         $vis fn $id(&self, $($arg: $arg_type),+) ->  $crate::anyhow::Result<$return_type>  {
354             use $crate::anyhow::Context;
355
356             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
357
358             self.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
359                 .context(::std::format!(
360                     "Error in {}, select_row_bound failed to execute or parse for: {}",
361                     ::std::stringify!($id),
362                     sql_stmt
363                 ))?
364                 .context(::std::format!(
365                     "Error in {}, select_row_bound expected single row result but found none for: {}",
366                     ::std::stringify!($id),
367                     sql_stmt
368                 ))
369         }
370    };
371    ($vis:vis fn async $id:ident($($arg:ident: $arg_type:ty),+) ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
372        $vis async fn $id(&self, $($arg: $arg_type),+) ->  $crate::anyhow::Result<$return_type>  {
373            use $crate::anyhow::Context;
374
375
376            self.write(|connection| {
377                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
378
379                connection.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
380                    .context(::std::format!(
381                        "Error in {}, select_row_bound failed to execute or parse for: {}",
382                        ::std::stringify!($id),
383                        sql_stmt
384                    ))?
385                    .context(::std::format!(
386                        "Error in {}, select_row_bound expected single row result but found none for: {}",
387                        ::std::stringify!($id),
388                        sql_stmt
389                    ))
390            }).await
391        }
392    };
393}