use anyhow::Result;
use rusqlite::Connection;
use rusqlite::OptionalExtension;
pub const FACE_RECLUSTER_WATERMARK: &str = "face_recluster_watermark";
fn ensure_table(conn: &Connection) -> rusqlite::Result<()> {
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS library_state (
key TEXT PRIMARY KEY,
value INTEGER NOT NULL
);",
)
}
pub fn get(conn: &Connection, key: &str) -> Result<Option<i64>> {
ensure_table(conn)?;
let v = conn
.query_row(
"SELECT value FROM library_state WHERE key = ?1",
[key],
|r| r.get(0),
)
.optional()?;
Ok(v)
}
pub fn set(conn: &Connection, key: &str, value: i64) -> Result<()> {
ensure_table(conn)?;
conn.execute(
"INSERT INTO library_state (key, value) VALUES (?1, ?2)
ON CONFLICT(key) DO UPDATE SET value = excluded.value",
rusqlite::params![key, value],
)?;
Ok(())
}
pub fn get_string(conn: &Connection, key: &str) -> Result<Option<String>> {
ensure_table(conn)?;
let v = conn
.query_row(
"SELECT value FROM library_state WHERE key = ?1",
[key],
|r| {
use rusqlite::types::ValueRef;
Ok(match r.get_ref(0)? {
ValueRef::Text(t) => String::from_utf8_lossy(t).into_owned(),
ValueRef::Integer(i) => i.to_string(),
ValueRef::Real(f) => f.to_string(),
ValueRef::Blob(b) => String::from_utf8_lossy(b).into_owned(),
ValueRef::Null => String::new(),
})
},
)
.optional()?;
Ok(v)
}
pub fn set_string(conn: &Connection, key: &str, value: &str) -> Result<()> {
ensure_table(conn)?;
conn.execute(
"INSERT INTO library_state (key, value) VALUES (?1, ?2)
ON CONFLICT(key) DO UPDATE SET value = excluded.value",
rusqlite::params![key, value],
)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use rusqlite::Connection;
#[test]
fn get_returns_none_before_the_first_set() {
let conn = Connection::open_in_memory().unwrap();
assert_eq!(get(&conn, FACE_RECLUSTER_WATERMARK).unwrap(), None);
}
#[test]
fn set_then_get_round_trips_and_overwrites() {
let conn = Connection::open_in_memory().unwrap();
set(&conn, FACE_RECLUSTER_WATERMARK, 7).unwrap();
set(&conn, FACE_RECLUSTER_WATERMARK, 42).unwrap();
assert_eq!(get(&conn, FACE_RECLUSTER_WATERMARK).unwrap(), Some(42));
}
#[test]
fn set_string_get_string_round_trips_text_and_coerced_numbers() {
let conn = Connection::open_in_memory().unwrap();
set_string(&conn, "fp", "v1:2:2:101.37:15.755").unwrap();
assert_eq!(
get_string(&conn, "fp").unwrap().as_deref(),
Some("v1:2:2:101.37:15.755")
);
set_string(&conn, "radius", "15").unwrap();
assert_eq!(get_string(&conn, "radius").unwrap().as_deref(), Some("15"));
assert_eq!(get_string(&conn, "missing").unwrap(), None);
}
#[test]
fn keys_are_independent() {
let conn = Connection::open_in_memory().unwrap();
set(&conn, "a", 1).unwrap();
set(&conn, "b", 2).unwrap();
assert_eq!(get(&conn, "a").unwrap(), Some(1));
assert_eq!(get(&conn, "b").unwrap(), Some(2));
}
}