db.rs

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