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_file_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
 58pub async fn open_memory_db<M: Migrator>(db_name: &str) -> ThreadSafeConnection<M> {
 59    ThreadSafeConnection::<M>::builder(db_name, false)
 60        .with_db_initialization_query(DB_INITIALIZE_QUERY)
 61        .with_connection_initialize_query(CONNECTION_INITIALIZE_QUERY)
 62        .build()
 63        .await
 64}
 65
 66/// Implements a basic DB wrapper for a given domain
 67#[macro_export]
 68macro_rules! connection {
 69    ($id:ident: $t:ident<$d:ty>) => {
 70        pub struct $t(::db::sqlez::thread_safe_connection::ThreadSafeConnection<$d>);
 71
 72        impl ::std::ops::Deref for $t {
 73            type Target = ::db::sqlez::thread_safe_connection::ThreadSafeConnection<$d>;
 74
 75            fn deref(&self) -> &Self::Target {
 76                &self.0
 77            }
 78        }
 79
 80        ::db::lazy_static::lazy_static! {
 81            pub static ref $id: $t = $t(if cfg!(any(test, feature = "test-support")) {
 82                $crate::smol::block_on(::db::open_memory_db(stringify!($id)))
 83            } else {
 84                $crate::smol::block_on(::db::open_file_db())
 85            });
 86        }
 87    };
 88}
 89
 90#[macro_export]
 91macro_rules! query {
 92    ($vis:vis fn $id:ident() -> Result<()> { $($sql:tt)+ }) => {
 93        $vis fn $id(&self) -> $crate::anyhow::Result<()> {
 94            use $crate::anyhow::Context;
 95
 96            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
 97
 98            self.exec(sql_stmt)?().context(::std::format!(
 99                "Error in {}, exec failed to execute or parse for: {}",
100                ::std::stringify!($id),
101                sql_stmt,
102            ))
103        }
104    };
105    ($vis:vis async fn $id:ident() -> Result<()> { $($sql:tt)+ }) => {
106        $vis async fn $id(&self) -> $crate::anyhow::Result<()> {
107            use $crate::anyhow::Context;
108
109
110            self.write(|connection| {
111                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
112
113                connection.exec(sql_stmt)?().context(::std::format!(
114                    "Error in {}, exec failed to execute or parse for: {}",
115                    ::std::stringify!($id),
116                    sql_stmt
117                ))
118            }).await
119        }
120    };
121    ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<()> { $($sql:tt)+ }) => {
122        $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> {
123            use $crate::anyhow::Context;
124
125            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
126
127            self.exec_bound::<($($arg_type),+)>(sql_stmt)?(($($arg),+))
128                .context(::std::format!(
129                    "Error in {}, exec_bound failed to execute or parse for: {}",
130                    ::std::stringify!($id),
131                    sql_stmt
132                ))
133        }
134    };
135    ($vis:vis async fn $id:ident($arg:ident: $arg_type:ty) -> Result<()> { $($sql:tt)+ }) => {
136        $vis async fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<()> {
137            use $crate::anyhow::Context;
138
139
140            self.write(move |connection| {
141                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
142
143                connection.exec_bound::<$arg_type>(sql_stmt)?($arg)
144                    .context(::std::format!(
145                        "Error in {}, exec_bound failed to execute or parse for: {}",
146                        ::std::stringify!($id),
147                        sql_stmt
148                    ))
149            }).await
150        }
151    };
152    ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<()> { $($sql:tt)+ }) => {
153        $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> {
154            use $crate::anyhow::Context;
155
156            self.write(move |connection| {
157                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
158
159                connection.exec_bound::<($($arg_type),+)>(sql_stmt)?(($($arg),+))
160                    .context(::std::format!(
161                        "Error in {}, exec_bound failed to execute or parse for: {}",
162                        ::std::stringify!($id),
163                        sql_stmt
164                    ))
165            }).await
166        }
167    };
168    ($vis:vis fn $id:ident() ->  Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
169         $vis fn $id(&self) -> $crate::anyhow::Result<Vec<$return_type>> {
170             use $crate::anyhow::Context;
171
172             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
173
174             self.select::<$return_type>(sql_stmt)?(())
175                 .context(::std::format!(
176                     "Error in {}, select_row failed to execute or parse for: {}",
177                     ::std::stringify!($id),
178                     sql_stmt
179                 ))
180         }
181    };
182    ($vis:vis async fn $id:ident() ->  Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
183        pub async fn $id(&self) -> $crate::anyhow::Result<Vec<$return_type>> {
184            use $crate::anyhow::Context;
185
186            self.write(|connection| {
187                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
188
189                connection.select::<$return_type>(sql_stmt)?(())
190                    .context(::std::format!(
191                        "Error in {}, select_row failed to execute or parse for: {}",
192                        ::std::stringify!($id),
193                        sql_stmt
194                    ))
195            }).await
196        }
197    };
198    ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
199         $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Vec<$return_type>> {
200             use $crate::anyhow::Context;
201
202             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
203
204             self.select_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
205                 .context(::std::format!(
206                     "Error in {}, exec_bound failed to execute or parse for: {}",
207                     ::std::stringify!($id),
208                     sql_stmt
209                 ))
210         }
211    };
212    ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
213        $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Vec<$return_type>> {
214            use $crate::anyhow::Context;
215
216            self.write(|connection| {
217                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
218
219                connection.select_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
220                    .context(::std::format!(
221                        "Error in {}, exec_bound failed to execute or parse for: {}",
222                        ::std::stringify!($id),
223                        sql_stmt
224                    ))
225            }).await
226        }
227    };
228    ($vis:vis fn $id:ident() ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
229         $vis fn $id(&self) -> $crate::anyhow::Result<Option<$return_type>> {
230             use $crate::anyhow::Context;
231
232             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
233
234             self.select_row::<$return_type>(sql_stmt)?()
235                 .context(::std::format!(
236                     "Error in {}, select_row failed to execute or parse for: {}",
237                     ::std::stringify!($id),
238                     sql_stmt
239                 ))
240         }
241    };
242    ($vis:vis async fn $id:ident() ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
243        $vis async fn $id(&self) -> $crate::anyhow::Result<Option<$return_type>> {
244            use $crate::anyhow::Context;
245
246            self.write(|connection| {
247                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
248
249                connection.select_row::<$return_type>(sql_stmt)?()
250                    .context(::std::format!(
251                        "Error in {}, select_row failed to execute or parse for: {}",
252                        ::std::stringify!($id),
253                        sql_stmt
254                    ))
255            }).await
256        }
257    };
258    ($vis:vis fn $id:ident($arg:ident: $arg_type:ty) ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
259        $vis fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<Option<$return_type>>  {
260            use $crate::anyhow::Context;
261
262            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
263
264            self.select_row_bound::<$arg_type, $return_type>(sql_stmt)?($arg)
265                .context(::std::format!(
266                    "Error in {}, select_row_bound failed to execute or parse for: {}",
267                    ::std::stringify!($id),
268                    sql_stmt
269                ))
270
271        }
272    };
273    ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
274         $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Option<$return_type>>  {
275             use $crate::anyhow::Context;
276
277             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
278
279             self.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
280                 .context(::std::format!(
281                     "Error in {}, select_row_bound failed to execute or parse for: {}",
282                     ::std::stringify!($id),
283                     sql_stmt
284                 ))
285
286         }
287    };
288    ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) ->  Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
289        $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Option<$return_type>>  {
290            use $crate::anyhow::Context;
291
292
293            self.write(|connection| {
294                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
295
296                connection.select_row_bound::<($($arg_type),+), $return_type>(indoc! { $sql })?(($($arg),+))
297                    .context(::std::format!(
298                        "Error in {}, select_row_bound failed to execute or parse for: {}",
299                        ::std::stringify!($id),
300                        sql_stmt
301                    ))
302            }).await
303        }
304    };
305    ($vis:vis fn $id:ident() ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
306         $vis fn $id(&self) ->  $crate::anyhow::Result<$return_type>  {
307             use $crate::anyhow::Context;
308
309             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
310
311             self.select_row::<$return_type>(indoc! { $sql })?()
312                 .context(::std::format!(
313                     "Error in {}, select_row_bound failed to execute or parse for: {}",
314                     ::std::stringify!($id),
315                     sql_stmt
316                 ))?
317                 .context(::std::format!(
318                     "Error in {}, select_row_bound expected single row result but found none for: {}",
319                     ::std::stringify!($id),
320                     sql_stmt
321                 ))
322         }
323    };
324    ($vis:vis async fn $id:ident() ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
325        $vis async fn $id(&self) ->  $crate::anyhow::Result<$return_type>  {
326            use $crate::anyhow::Context;
327
328            self.write(|connection| {
329                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
330
331                connection.select_row::<$return_type>(sql_stmt)?()
332                    .context(::std::format!(
333                        "Error in {}, select_row_bound failed to execute or parse for: {}",
334                        ::std::stringify!($id),
335                        sql_stmt
336                    ))?
337                    .context(::std::format!(
338                        "Error in {}, select_row_bound expected single row result but found none for: {}",
339                        ::std::stringify!($id),
340                        sql_stmt
341                    ))
342            }).await
343        }
344    };
345    ($vis:vis fn $id:ident($arg:ident: $arg_type:ty) ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
346        pub fn $id(&self, $arg: $arg_type) ->  $crate::anyhow::Result<$return_type>  {
347            use $crate::anyhow::Context;
348
349            let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
350
351            self.select_row_bound::<$arg_type, $return_type>(sql_stmt)?($arg)
352                .context(::std::format!(
353                    "Error in {}, select_row_bound failed to execute or parse for: {}",
354                    ::std::stringify!($id),
355                    sql_stmt
356                ))?
357                .context(::std::format!(
358                    "Error in {}, select_row_bound expected single row result but found none for: {}",
359                    ::std::stringify!($id),
360                    sql_stmt
361                ))
362        }
363    };
364    ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
365         $vis fn $id(&self, $($arg: $arg_type),+) ->  $crate::anyhow::Result<$return_type>  {
366             use $crate::anyhow::Context;
367
368             let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
369
370             self.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
371                 .context(::std::format!(
372                     "Error in {}, select_row_bound failed to execute or parse for: {}",
373                     ::std::stringify!($id),
374                     sql_stmt
375                 ))?
376                 .context(::std::format!(
377                     "Error in {}, select_row_bound expected single row result but found none for: {}",
378                     ::std::stringify!($id),
379                     sql_stmt
380                 ))
381         }
382    };
383    ($vis:vis fn async $id:ident($($arg:ident: $arg_type:ty),+) ->  Result<$return_type:ty> { $($sql:tt)+ }) => {
384        $vis async fn $id(&self, $($arg: $arg_type),+) ->  $crate::anyhow::Result<$return_type>  {
385            use $crate::anyhow::Context;
386
387
388            self.write(|connection| {
389                let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
390
391                connection.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
392                    .context(::std::format!(
393                        "Error in {}, select_row_bound failed to execute or parse for: {}",
394                        ::std::stringify!($id),
395                        sql_stmt
396                    ))?
397                    .context(::std::format!(
398                        "Error in {}, select_row_bound expected single row result but found none for: {}",
399                        ::std::stringify!($id),
400                        sql_stmt
401                    ))
402            }).await
403        }
404    };
405}