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