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