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