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
117 self.write(|connection| {
118 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
119
120 connection.exec(sql_stmt)?().context(::std::format!(
121 "Error in {}, exec failed to execute or parse for: {}",
122 ::std::stringify!($id),
123 sql_stmt
124 ))
125 }).await
126 }
127 };
128 ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<()> { $($sql:tt)+ }) => {
129 $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> {
130 use $crate::anyhow::Context;
131
132 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
133
134 self.exec_bound::<($($arg_type),+)>(sql_stmt)?(($($arg),+))
135 .context(::std::format!(
136 "Error in {}, exec_bound failed to execute or parse for: {}",
137 ::std::stringify!($id),
138 sql_stmt
139 ))
140 }
141 };
142 ($vis:vis async fn $id:ident($arg:ident: $arg_type:ty) -> Result<()> { $($sql:tt)+ }) => {
143 $vis async fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<()> {
144 use $crate::anyhow::Context;
145
146
147 self.write(move |connection| {
148 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
149
150 connection.exec_bound::<$arg_type>(sql_stmt)?($arg)
151 .context(::std::format!(
152 "Error in {}, exec_bound failed to execute or parse for: {}",
153 ::std::stringify!($id),
154 sql_stmt
155 ))
156 }).await
157 }
158 };
159 ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<()> { $($sql:tt)+ }) => {
160 $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> {
161 use $crate::anyhow::Context;
162
163 self.write(move |connection| {
164 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
165
166 connection.exec_bound::<($($arg_type),+)>(sql_stmt)?(($($arg),+))
167 .context(::std::format!(
168 "Error in {}, exec_bound failed to execute or parse for: {}",
169 ::std::stringify!($id),
170 sql_stmt
171 ))
172 }).await
173 }
174 };
175 ($vis:vis fn $id:ident() -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
176 $vis fn $id(&self) -> $crate::anyhow::Result<Vec<$return_type>> {
177 use $crate::anyhow::Context;
178
179 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
180
181 self.select::<$return_type>(sql_stmt)?(())
182 .context(::std::format!(
183 "Error in {}, select_row failed to execute or parse for: {}",
184 ::std::stringify!($id),
185 sql_stmt
186 ))
187 }
188 };
189 ($vis:vis async fn $id:ident() -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
190 pub async fn $id(&self) -> $crate::anyhow::Result<Vec<$return_type>> {
191 use $crate::anyhow::Context;
192
193 self.write(|connection| {
194 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
195
196 connection.select::<$return_type>(sql_stmt)?(())
197 .context(::std::format!(
198 "Error in {}, select_row failed to execute or parse for: {}",
199 ::std::stringify!($id),
200 sql_stmt
201 ))
202 }).await
203 }
204 };
205 ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
206 $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Vec<$return_type>> {
207 use $crate::anyhow::Context;
208
209 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
210
211 self.select_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
212 .context(::std::format!(
213 "Error in {}, exec_bound failed to execute or parse for: {}",
214 ::std::stringify!($id),
215 sql_stmt
216 ))
217 }
218 };
219 ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Vec<$return_type:ty>> { $($sql:tt)+ }) => {
220 $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Vec<$return_type>> {
221 use $crate::anyhow::Context;
222
223 self.write(|connection| {
224 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
225
226 connection.select_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
227 .context(::std::format!(
228 "Error in {}, exec_bound failed to execute or parse for: {}",
229 ::std::stringify!($id),
230 sql_stmt
231 ))
232 }).await
233 }
234 };
235 ($vis:vis fn $id:ident() -> Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
236 $vis fn $id(&self) -> $crate::anyhow::Result<Option<$return_type>> {
237 use $crate::anyhow::Context;
238
239 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
240
241 self.select_row::<$return_type>(sql_stmt)?()
242 .context(::std::format!(
243 "Error in {}, select_row failed to execute or parse for: {}",
244 ::std::stringify!($id),
245 sql_stmt
246 ))
247 }
248 };
249 ($vis:vis async fn $id:ident() -> Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
250 $vis async fn $id(&self) -> $crate::anyhow::Result<Option<$return_type>> {
251 use $crate::anyhow::Context;
252
253 self.write(|connection| {
254 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
255
256 connection.select_row::<$return_type>(sql_stmt)?()
257 .context(::std::format!(
258 "Error in {}, select_row failed to execute or parse for: {}",
259 ::std::stringify!($id),
260 sql_stmt
261 ))
262 }).await
263 }
264 };
265 ($vis:vis fn $id:ident($arg:ident: $arg_type:ty) -> Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
266 $vis fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<Option<$return_type>> {
267 use $crate::anyhow::Context;
268
269 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
270
271 self.select_row_bound::<$arg_type, $return_type>(sql_stmt)?($arg)
272 .context(::std::format!(
273 "Error in {}, select_row_bound failed to execute or parse for: {}",
274 ::std::stringify!($id),
275 sql_stmt
276 ))
277
278 }
279 };
280 ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
281 $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Option<$return_type>> {
282 use $crate::anyhow::Context;
283
284 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
285
286 self.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
287 .context(::std::format!(
288 "Error in {}, select_row_bound failed to execute or parse for: {}",
289 ::std::stringify!($id),
290 sql_stmt
291 ))
292
293 }
294 };
295 ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<Option<$return_type:ty>> { $($sql:tt)+ }) => {
296 $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<Option<$return_type>> {
297 use $crate::anyhow::Context;
298
299
300 self.write(|connection| {
301 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
302
303 connection.select_row_bound::<($($arg_type),+), $return_type>(indoc! { $sql })?(($($arg),+))
304 .context(::std::format!(
305 "Error in {}, select_row_bound failed to execute or parse for: {}",
306 ::std::stringify!($id),
307 sql_stmt
308 ))
309 }).await
310 }
311 };
312 ($vis:vis fn $id:ident() -> Result<$return_type:ty> { $($sql:tt)+ }) => {
313 $vis fn $id(&self) -> $crate::anyhow::Result<$return_type> {
314 use $crate::anyhow::Context;
315
316 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
317
318 self.select_row::<$return_type>(indoc! { $sql })?()
319 .context(::std::format!(
320 "Error in {}, select_row_bound failed to execute or parse for: {}",
321 ::std::stringify!($id),
322 sql_stmt
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_stmt
328 ))
329 }
330 };
331 ($vis:vis async fn $id:ident() -> Result<$return_type:ty> { $($sql:tt)+ }) => {
332 $vis async fn $id(&self) -> $crate::anyhow::Result<$return_type> {
333 use $crate::anyhow::Context;
334
335 self.write(|connection| {
336 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
337
338 connection.select_row::<$return_type>(sql_stmt)?()
339 .context(::std::format!(
340 "Error in {}, select_row_bound failed to execute or parse for: {}",
341 ::std::stringify!($id),
342 sql_stmt
343 ))?
344 .context(::std::format!(
345 "Error in {}, select_row_bound expected single row result but found none for: {}",
346 ::std::stringify!($id),
347 sql_stmt
348 ))
349 }).await
350 }
351 };
352 ($vis:vis fn $id:ident($arg:ident: $arg_type:ty) -> Result<$return_type:ty> { $($sql:tt)+ }) => {
353 pub 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 $id:ident($($arg:ident: $arg_type:ty),+) -> Result<$return_type:ty> { $($sql:tt)+ }) => {
372 $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<$return_type> {
373 use $crate::anyhow::Context;
374
375 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
376
377 self.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
378 .context(::std::format!(
379 "Error in {}, select_row_bound failed to execute or parse for: {}",
380 ::std::stringify!($id),
381 sql_stmt
382 ))?
383 .context(::std::format!(
384 "Error in {}, select_row_bound expected single row result but found none for: {}",
385 ::std::stringify!($id),
386 sql_stmt
387 ))
388 }
389 };
390 ($vis:vis fn async $id:ident($($arg:ident: $arg_type:ty),+) -> Result<$return_type:ty> { $($sql:tt)+ }) => {
391 $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<$return_type> {
392 use $crate::anyhow::Context;
393
394
395 self.write(|connection| {
396 let sql_stmt = $crate::sqlez_macros::sql!($($sql)+);
397
398 connection.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+))
399 .context(::std::format!(
400 "Error in {}, select_row_bound failed to execute or parse for: {}",
401 ::std::stringify!($id),
402 sql_stmt
403 ))?
404 .context(::std::format!(
405 "Error in {}, select_row_bound expected single row result but found none for: {}",
406 ::std::stringify!($id),
407 sql_stmt
408 ))
409 }).await
410 }
411 };
412}