213 lines
5.9 KiB
Rust
213 lines
5.9 KiB
Rust
use serde::Serialize;
|
|
use serde::de::DeserializeOwned;
|
|
use sqlx::SqlitePool;
|
|
|
|
use crate::errors::*;
|
|
|
|
|
|
pub async fn save<T>(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<T>(pool: &SqlitePool, name: &str) -> Result<Option<T>, 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<Option<Vec<u8>>, 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::<Vec<&str>>()
|
|
.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<Option<_>>, 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);
|
|
}
|
|
}
|