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