db.rs

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