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
117            self.write(|connection| {
118                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
119
120                connection.exec(sql_stmt)?().context(::std::format!(
121                    "Error in {}, exec failed to execute or parse for: {}",
122                    ::std::stringify!($id),
123                    sql_stmt
124                ))
125            }).await
126        }
127    };
128    ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<()> { $($sql:tt)+ }) => {
129        $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> {
130            use $crate::anyhow::Context;
131
132            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
133
134            self.exec_bound::<($($arg_type),+)>(sql_stmt)?(($($arg),+))
135                .context(::std::format!(
136                    "Error in {}, exec_bound failed to execute or parse for: {}",
137                    ::std::stringify!($id),
138                    sql_stmt
139                ))
140        }
141    };
142    ($vis:vis async fn $id:ident($arg:ident: $arg_type:ty) -> Result<()> { $($sql:tt)+ }) => {
143        $vis async fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<()> {
144            use $crate::anyhow::Context;
145
146
147            self.write(move |connection| {
148                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
149
150                connection.exec_bound::<$arg_type>(sql_stmt)?($arg)
151                    .context(::std::format!(
152                        "Error in {}, exec_bound failed to execute or parse for: {}",
153                        ::std::stringify!($id),
154                        sql_stmt
155                    ))
156            }).await
157        }
158    };
159    ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<()> { $($sql:tt)+ }) => {
160        $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> {
161            use $crate::anyhow::Context;
162
163            self.write(move |connection| {
164                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
165
166                connection.exec_bound::<($($arg_type),+)>(sql_stmt)?(($($arg),+))
167                    .context(::std::format!(
168                        "Error in {}, exec_bound failed to execute or parse for: {}",
169                        ::std::stringify!($id),
170                        sql_stmt
171                    ))
172            }).await
173        }
174    };
175    ($vis:vis fn $id:ident() ->  Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
176         $vis fn $id(&self) -> $crate::anyhow::Result<Vec<$return_type>> {
177             use $crate::anyhow::Context;
178
179             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
180
181             self.select::<$return_type>(sql_stmt)?(())
182                 .context(::std::format!(
183                     "Error in {}, select_row failed to execute or parse for: {}",
184                     ::std::stringify!($id),
185                     sql_stmt
186                 ))
187         }
188    };
189    ($vis:vis async fn $id:ident() ->  Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
190        pub async fn $id(&self) -> $crate::anyhow::Result<Vec<$return_type>> {
191            use $crate::anyhow::Context;
192
193            self.write(|connection| {
194                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
195
196                connection.select::<$return_type>(sql_stmt)?(())
197                    .context(::std::format!(
198                        "Error in {}, select_row failed to execute or parse for: {}",
199                        ::std::stringify!($id),
200                        sql_stmt
201                    ))
202            }).await
203        }
204    };
205    ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
206         $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Vec<$return_type>> {
207             use $crate::anyhow::Context;
208
209             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
210
211             self.select_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
212                 .context(::std::format!(
213                     "Error in {}, exec_bound failed to execute or parse for: {}",
214                     ::std::stringify!($id),
215                     sql_stmt
216                 ))
217         }
218    };
219    ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
220        $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Vec<$return_type>> {
221            use $crate::anyhow::Context;
222
223            self.write(|connection| {
224                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
225
226                connection.select_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
227                    .context(::std::format!(
228                        "Error in {}, exec_bound failed to execute or parse for: {}",
229                        ::std::stringify!($id),
230                        sql_stmt
231                    ))
232            }).await
233        }
234    };
235    ($vis:vis fn $id:ident() ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
236         $vis fn $id(&self) -> $crate::anyhow::Result<Option<$return_type>> {
237             use $crate::anyhow::Context;
238
239             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
240
241             self.select_row::<$return_type>(sql_stmt)?()
242                 .context(::std::format!(
243                     "Error in {}, select_row failed to execute or parse for: {}",
244                     ::std::stringify!($id),
245                     sql_stmt
246                 ))
247         }
248    };
249    ($vis:vis async fn $id:ident() ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
250        $vis async fn $id(&self) -> $crate::anyhow::Result<Option<$return_type>> {
251            use $crate::anyhow::Context;
252
253            self.write(|connection| {
254                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
255
256                connection.select_row::<$return_type>(sql_stmt)?()
257                    .context(::std::format!(
258                        "Error in {}, select_row failed to execute or parse for: {}",
259                        ::std::stringify!($id),
260                        sql_stmt
261                    ))
262            }).await
263        }
264    };
265    ($vis:vis fn $id:ident($arg:ident: $arg_type:ty) ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
266        $vis fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<Option<$return_type>>  {
267            use $crate::anyhow::Context;
268
269            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
270
271            self.select_row_bound::<$arg_type, $return_type>(sql_stmt)?($arg)
272                .context(::std::format!(
273                    "Error in {}, select_row_bound failed to execute or parse for: {}",
274                    ::std::stringify!($id),
275                    sql_stmt
276                ))
277
278        }
279    };
280    ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
281         $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Option<$return_type>>  {
282             use $crate::anyhow::Context;
283
284             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
285
286             self.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
287                 .context(::std::format!(
288                     "Error in {}, select_row_bound failed to execute or parse for: {}",
289                     ::std::stringify!($id),
290                     sql_stmt
291                 ))
292
293         }
294    };
295    ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
296        $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Option<$return_type>>  {
297            use $crate::anyhow::Context;
298
299
300            self.write(|connection| {
301                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
302
303                connection.select_row_bound::<($($arg_type),+), $return_type>(indoc! { $sql })?(($($arg),+))
304                    .context(::std::format!(
305                        "Error in {}, select_row_bound failed to execute or parse for: {}",
306                        ::std::stringify!($id),
307                        sql_stmt
308                    ))
309            }).await
310        }
311    };
312    ($vis:vis fn $id:ident() ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
313         $vis fn $id(&self) ->  $crate::anyhow::Result<$return_type>  {
314             use $crate::anyhow::Context;
315
316             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
317
318             self.select_row::<$return_type>(indoc! { $sql })?()
319                 .context(::std::format!(
320                     "Error in {}, select_row_bound failed to execute or parse for: {}",
321                     ::std::stringify!($id),
322                     sql_stmt
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_stmt
328                 ))
329         }
330    };
331    ($vis:vis async fn $id:ident() ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
332        $vis async fn $id(&self) ->  $crate::anyhow::Result<$return_type>  {
333            use $crate::anyhow::Context;
334
335            self.write(|connection| {
336                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
337
338                connection.select_row::<$return_type>(sql_stmt)?()
339                    .context(::std::format!(
340                        "Error in {}, select_row_bound failed to execute or parse for: {}",
341                        ::std::stringify!($id),
342                        sql_stmt
343                    ))?
344                    .context(::std::format!(
345                        "Error in {}, select_row_bound expected single row result but found none for: {}",
346                        ::std::stringify!($id),
347                        sql_stmt
348                    ))
349            }).await
350        }
351    };
352    ($vis:vis fn $id:ident($arg:ident: $arg_type:ty) ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
353        pub 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 $id:ident($($arg:ident: $arg_type:ty),+) ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
372         $vis fn $id(&self, $($arg: $arg_type),+) ->  $crate::anyhow::Result<$return_type>  {
373             use $crate::anyhow::Context;
374
375             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
376
377             self.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
378                 .context(::std::format!(
379                     "Error in {}, select_row_bound failed to execute or parse for: {}",
380                     ::std::stringify!($id),
381                     sql_stmt
382                 ))?
383                 .context(::std::format!(
384                     "Error in {}, select_row_bound expected single row result but found none for: {}",
385                     ::std::stringify!($id),
386                     sql_stmt
387                 ))
388         }
389    };
390    ($vis:vis fn async $id:ident($($arg:ident: $arg_type:ty),+) ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
391        $vis async fn $id(&self, $($arg: $arg_type),+) ->  $crate::anyhow::Result<$return_type>  {
392            use $crate::anyhow::Context;
393
394
395            self.write(|connection| {
396                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
397
398                connection.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
399                    .context(::std::format!(
400                        "Error in {}, select_row_bound failed to execute or parse for: {}",
401                        ::std::stringify!($id),
402                        sql_stmt
403                    ))?
404                    .context(::std::format!(
405                        "Error in {}, select_row_bound expected single row result but found none for: {}",
406                        ::std::stringify!($id),
407                        sql_stmt
408                    ))
409            }).await
410        }
411    };
412}