db.rs

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