db.rs

  1pub mod kvp;
  2
  3// Re-export
  4pub use anyhow;
  5pub use indoc::indoc;
  6pub use lazy_static;
  7pub use sqlez;
  8
  9#[cfg(any(test, feature = "test-support"))]
 10use anyhow::Result;
 11#[cfg(any(test, feature = "test-support"))]
 12use sqlez::connection::Connection;
 13#[cfg(any(test, feature = "test-support"))]
 14use sqlez::domain::Domain;
 15
 16use sqlez::domain::Migrator;
 17use sqlez::thread_safe_connection::ThreadSafeConnection;
 18use std::fs::{create_dir_all, remove_dir_all};
 19use std::path::Path;
 20use util::channel::{ReleaseChannel, RELEASE_CHANNEL, RELEASE_CHANNEL_NAME};
 21use util::paths::DB_DIR;
 22
 23const INITIALIZE_QUERY: &'static str = indoc! {"
 24    PRAGMA journal_mode=WAL;
 25    PRAGMA synchronous=NORMAL;
 26    PRAGMA busy_timeout=1;
 27    PRAGMA foreign_keys=TRUE;
 28    PRAGMA case_sensitive_like=TRUE;
 29"};
 30
 31/// Open or create a database at the given directory path.
 32pub fn open_file_db<M: Migrator>() -> ThreadSafeConnection<M> {
 33    // Use 0 for now. Will implement incrementing and clearing of old db files soon TM
 34    let current_db_dir = (*DB_DIR).join(Path::new(&format!("0-{}", *RELEASE_CHANNEL_NAME)));
 35
 36    if *RELEASE_CHANNEL == ReleaseChannel::Dev && std::env::var("WIPE_DB").is_ok() {
 37        remove_dir_all(&current_db_dir).ok();
 38    }
 39
 40    create_dir_all(&current_db_dir).expect("Should be able to create the database directory");
 41    let db_path = current_db_dir.join(Path::new("db.sqlite"));
 42
 43    ThreadSafeConnection::new(Some(db_path.to_string_lossy().as_ref()), true)
 44        .with_initialize_query(INITIALIZE_QUERY)
 45}
 46
 47pub fn open_memory_db<M: Migrator>(db_name: Option<&str>) -> ThreadSafeConnection<M> {
 48    ThreadSafeConnection::new(db_name, false).with_initialize_query(INITIALIZE_QUERY)
 49}
 50
 51#[cfg(any(test, feature = "test-support"))]
 52pub fn write_db_to<D: Domain, P: AsRef<Path>>(
 53    conn: &ThreadSafeConnection<D>,
 54    dest: P,
 55) -> Result<()> {
 56    let destination = Connection::open_file(dest.as_ref().to_string_lossy().as_ref());
 57    conn.backup_main(&destination)
 58}
 59
 60/// Implements a basic DB wrapper for a given domain
 61#[macro_export]
 62macro_rules! connection {
 63    ($id:ident: $t:ident<$d:ty>) => {
 64        pub struct $t(::db::sqlez::thread_safe_connection::ThreadSafeConnection<$d>);
 65
 66        impl ::std::ops::Deref for $t {
 67            type Target = ::db::sqlez::thread_safe_connection::ThreadSafeConnection<$d>;
 68
 69            fn deref(&self) -> &Self::Target {
 70                &self.0
 71            }
 72        }
 73
 74        ::db::lazy_static::lazy_static! {
 75            pub static ref $id: $t = $t(if cfg!(any(test, feature = "test-support")) {
 76                ::db::open_memory_db(None)
 77            } else {
 78                ::db::open_file_db()
 79            });
 80        }
 81    };
 82}
 83
 84#[macro_export]
 85macro_rules! sql_method {
 86    ($id:ident() ->  Result<()>: $sql:expr) => {
 87        pub fn $id(&self) -> $crate::sqlez::anyhow::Result<()> {
 88            use $crate::anyhow::Context;
 89
 90            self.exec($sql)?().context(::std::format!(
 91                "Error in {}, exec failed to execute or parse for: {}",
 92                ::std::stringify!($id),
 93                ::std::stringify!($sql),
 94            ))
 95        }
 96    };
 97    ($id:ident($($arg:ident: $arg_type:ty),+) -> Result<()>: $sql:expr) => {
 98        pub fn $id(&self, $($arg: $arg_type),+) -> $crate::sqlez::anyhow::Result<()> {
 99            use $crate::anyhow::Context;
100
101            self.exec_bound::<($($arg_type),+)>($sql)?(($($arg),+))
102                .context(::std::format!(
103                    "Error in {}, exec_bound failed to execute or parse for: {}",
104                    ::std::stringify!($id),
105                    ::std::stringify!($sql),
106                ))
107        }
108    };
109    ($id:ident() ->  Result<Vec<$return_type:ty>>: $sql:expr) => {
110         pub fn $id(&self) -> $crate::sqlez::anyhow::Result<Vec<$return_type>> {
111             use $crate::anyhow::Context;
112
113             self.select::<$return_type>($sql)?(())
114                 .context(::std::format!(
115                     "Error in {}, select_row failed to execute or parse for: {}",
116                     ::std::stringify!($id),
117                     ::std::stringify!($sql),
118                 ))
119         }
120    };
121    ($id:ident($($arg:ident: $arg_type:ty),+) -> Result<Vec<$return_type:ty>>: $sql:expr) => {
122         pub fn $id(&self, $($arg: $arg_type),+) -> $crate::sqlez::anyhow::Result<Vec<$return_type>> {
123             use $crate::anyhow::Context;
124
125             self.select_bound::<($($arg_type),+), $return_type>($sql)?(($($arg),+))
126                 .context(::std::format!(
127                     "Error in {}, exec_bound failed to execute or parse for: {}",
128                     ::std::stringify!($id),
129                     ::std::stringify!($sql),
130                 ))
131         }
132    };
133    ($id:ident() ->  Result<Option<$return_type:ty>>: $sql:expr) => {
134         pub fn $id(&self) -> $crate::sqlez::anyhow::Result<Option<$return_type>> {
135             use $crate::anyhow::Context;
136
137             self.select_row::<$return_type>($sql)?()
138                 .context(::std::format!(
139                     "Error in {}, select_row failed to execute or parse for: {}",
140                     ::std::stringify!($id),
141                     ::std::stringify!($sql),
142                 ))
143         }
144    };
145    ($id:ident($($arg:ident: $arg_type:ty),+) ->  Result<Option<$return_type:ty>>: $sql:expr) => {
146         pub fn $id(&self, $($arg: $arg_type),+) -> $crate::sqlez::anyhow::Result<Option<$return_type>>  {
147             use $crate::anyhow::Context;
148
149             self.select_row_bound::<($($arg_type),+), $return_type>($sql)?(($($arg),+))
150                 .context(::std::format!(
151                     "Error in {}, select_row_bound failed to execute or parse for: {}",
152                     ::std::stringify!($id),
153                     ::std::stringify!($sql),
154                 ))
155
156         }
157    };
158    ($id:ident() ->  Result<$return_type:ty>>: $sql:expr) => {
159         pub fn $id(&self) ->  $crate::sqlez::anyhow::Result<$return_type>  {
160             use $crate::anyhow::Context;
161
162             self.select_row::<$return_type>($sql)?(($($arg),+))
163                 .context(::std::format!(
164                     "Error in {}, select_row_bound failed to execute or parse for: {}",
165                     ::std::stringify!($id),
166                     ::std::stringify!($sql),
167                 ))?
168                 .context(::std::format!(
169                     "Error in {}, select_row_bound expected single row result but found none for: {}",
170                     ::std::stringify!($id),
171                     ::std::stringify!($sql),
172                 ))
173         }
174    };
175    ($id:ident($($arg:ident: $arg_type:ty),+) ->  Result<$return_type:ty>>: $sql:expr) => {
176         pub fn $id(&self, $($arg: $arg_type),+) ->  $crate::sqlez::anyhow::Result<$return_type>  {
177             use $crate::anyhow::Context;
178
179             self.select_row_bound::<($($arg_type),+), $return_type>($sql)?(($($arg),+))
180                 .context(::std::format!(
181                     "Error in {}, select_row_bound failed to execute or parse for: {}",
182                     ::std::stringify!($id),
183                     ::std::stringify!($sql),
184                 ))?
185                 .context(::std::format!(
186                     "Error in {}, select_row_bound expected single row result but found none for: {}",
187                     ::std::stringify!($id),
188                     ::std::stringify!($sql),
189                 ))
190         }
191    };
192}