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