use serde::Serialize; use serde::de::DeserializeOwned; use sqlx::SqlitePool; use crate::errors::*; pub async fn save(pool: &SqlitePool, name: &str, value: &T) -> Result<(), sqlx::Error> where T: Serialize + ?Sized { let bytes = serde_json::to_vec(value).unwrap(); save_bytes(pool, name, &bytes).await } pub async fn save_bytes(pool: &SqlitePool, name: &str, bytes: &[u8]) -> Result<(), sqlx::Error> { sqlx::query!( "INSERT INTO kv (name, value) VALUES (?, ?) ON CONFLICT(name) DO UPDATE SET value = excluded.value;", name, bytes, ).execute(pool).await?; Ok(()) } pub async fn load(pool: &SqlitePool, name: &str) -> Result, LoadKvError> where T: DeserializeOwned { let v = load_bytes(pool, name) .await? .map(|bytes| serde_json::from_slice(&bytes)) .transpose()?; Ok(v) } pub async fn load_bytes(pool: &SqlitePool, name: &str) -> Result>, sqlx::Error> { sqlx::query!("SELECT name, value FROM kv WHERE name = ?", name) .map(|row| row.value) .fetch_optional(pool) .await .map(|o| o.flatten()) } // we don't have a need for this right now, but we will some day #[cfg(test)] pub async fn delete(pool: &SqlitePool, name: &str) -> Result<(), sqlx::Error> { sqlx::query!("DELETE FROM kv WHERE name = ?", name) .execute(pool) .await?; Ok(()) } pub async fn delete_multi(pool: &SqlitePool, names: &[&str]) -> Result<(), sqlx::Error> { let placeholder = names.iter() .map(|_| "?") .collect::>() .join(","); let query = format!("DELETE FROM kv WHERE name IN ({})", placeholder); let mut q = sqlx::query(&query); for name in names { q = q.bind(name); } q.execute(pool).await?; Ok(()) } macro_rules! load_bytes_multi { ( $pool:expr, $($name:literal),* ) => { // wrap everything up in an async block for easy short-circuiting... async { // ...returning a Result... Ok::<_, sqlx::Error>( //containing an Option... Some( // containing a tuple... ( // ...with one item for each repetition of $name $( // load_bytes returns Result>, the Result is handled by // the ? and we match on the Option match crate::kv::load_bytes($pool, $name).await? { Some(v) => v, None => return Ok(None) }, )* ) ) ) } } } pub(crate) use load_bytes_multi; // macro_rules! load_multi { // ( // $pool:expr, // $($name:literal),* // ) => { // (|| { // ( // $( // match load(pool, $name)? { // Some(v) => v, // None => return Ok(None) // }, // )* // ) // })() // } // } #[cfg(test)] mod tests { use super::*; #[sqlx::test] async fn test_save_bytes(pool: SqlitePool) { save_bytes(&pool, "test_bytes", b"hello world").await .expect("Failed to save bytes"); } #[sqlx::test] async fn test_save(pool: SqlitePool) { save(&pool, "test_string", "hello world").await .expect("Failed to save string"); save(&pool, "test_int", &123).await .expect("Failed to save integer"); save(&pool, "test_bool", &true).await .expect("Failed to save bool"); } #[sqlx::test(fixtures("kv"))] async fn test_load_bytes(pool: SqlitePool) { let bytes = load_bytes(&pool, "test_bytes").await .expect("Failed to load bytes") .expect("Test data not found in database"); assert_eq!(bytes, Vec::from(b"hello world")); } #[sqlx::test(fixtures("kv"))] async fn test_load(pool: SqlitePool) { let string: String = load(&pool, "test_string").await .expect("Failed to load string") .expect("Test data not found in database"); assert_eq!(string, "hello world".to_string()); let integer: usize = load(&pool, "test_int").await .expect("Failed to load integer") .expect("Test data not found in database"); assert_eq!(integer, 123); let boolean: bool = load(&pool, "test_bool").await .expect("Failed to load boolean") .expect("Test data not found in database"); assert_eq!(boolean, true); } #[sqlx::test(fixtures("kv"))] async fn test_load_multi(pool: SqlitePool) { let (bytes, boolean) = load_bytes_multi!(&pool, "test_bytes", "test_bool") .await .expect("Failed to load items") .expect("Test data not found in database"); assert_eq!(bytes, Vec::from(b"hello world")); assert_eq!(boolean, Vec::from(b"true")); } #[sqlx::test(fixtures("kv"))] async fn test_delete(pool: SqlitePool) { delete(&pool, "test_bytes").await .expect("Failed to delete data"); let loaded = load_bytes(&pool, "test_bytes").await .expect("Failed to load data"); assert_eq!(loaded, None); } #[sqlx::test(fixtures("kv"))] async fn test_delete_multi(pool: SqlitePool) { delete_multi(&pool, &["test_bytes", "test_string"]).await .expect("Failed to delete keys"); let bytes_opt = load_bytes(&pool, "test_bytes").await .expect("Failed to load bytes"); assert_eq!(bytes_opt, None); let string_opt = load_bytes(&pool, "test_string").await .expect("Failed to load string"); assert_eq!(string_opt, None); } }