use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::path::Path;
pub type ModuleHash = [u8; 32];
pub(crate) fn compute_hash(raw_bytes: &[u8]) -> ModuleHash {
Sha256::digest(raw_bytes).into()
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct CacheData {
pub sig: u32,
pub name: String,
pub body: Vec<u8>,
}
pub(crate) struct Cache {
module_hash: ModuleHash,
db: Option<sqlite::ConnectionThreadSafe>,
db_ro: Option<sqlite::ConnectionThreadSafe>,
}
pub(crate) struct CacheThreadCtx<'a> {
cache: &'a Cache,
lookup_stmt: Option<sqlite::Statement<'a>>,
insert_stmt: Option<sqlite::Statement<'a>>,
ro_lookup_stmt: Option<sqlite::Statement<'a>>,
}
impl Cache {
pub fn open(
path: Option<&Path>,
path_ro: Option<&Path>,
module_hash: ModuleHash,
) -> anyhow::Result<Cache> {
let db = match path {
Some(path) => {
let db = sqlite::Connection::open_thread_safe(path)?;
db.execute(
r#"
CREATE TABLE IF NOT EXISTS weval_cache(
module_hash BLOB NOT NULL,
key BLOB NOT NULL,
result BLOB NOT NULL,
created_time INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx ON weval_cache(
module_hash, key
);
"#,
)?;
Some(db)
}
None => None,
};
let db_ro = match path_ro {
Some(path_ro) => Some(sqlite::Connection::open_thread_safe_with_flags(
path_ro,
sqlite::OpenFlags::new().with_read_only(),
)?),
None => None,
};
Ok(Cache {
module_hash,
db,
db_ro,
})
}
pub fn can_insert(&self) -> bool {
self.db.is_some()
}
pub fn thread(&self) -> anyhow::Result<CacheThreadCtx<'_>> {
let lookup_stmt = match self.db.as_ref() {
Some(db) => Some(db.prepare(
r#"
SELECT result FROM weval_cache
WHERE module_hash=? AND key=?
"#,
)?),
None => None,
};
let ro_lookup_stmt = match self.db_ro.as_ref() {
Some(db_ro) => Some(db_ro.prepare(
r#"
SELECT result FROM weval_cache
WHERE module_hash=? AND key=?
"#,
)?),
None => None,
};
let insert_stmt = match self.db.as_ref() {
Some(db) => Some(db.prepare(
r#"
INSERT INTO weval_cache
(module_hash, key, result, created_time)
VALUES
(?, ?, ?, unixepoch())
"#,
)?),
None => None,
};
Ok(CacheThreadCtx {
cache: self,
lookup_stmt,
insert_stmt,
ro_lookup_stmt,
})
}
}
impl<'a> CacheThreadCtx<'a> {
pub fn lookup(&mut self, key: &[u8]) -> anyhow::Result<Option<CacheData>> {
let mut result = None;
for lookup in self
.ro_lookup_stmt
.iter_mut()
.chain(self.lookup_stmt.iter_mut())
{
lookup.bind((1, &self.cache.module_hash[..]))?;
lookup.bind((2, key))?;
while lookup.next()? == sqlite::State::Row {
let data: Vec<u8> = lookup.read(0)?;
result = Some(bincode::deserialize(&data)?);
}
lookup.reset()?;
if result.is_some() {
break;
}
}
Ok(result)
}
pub fn insert(&mut self, key: &[u8], data: CacheData) -> anyhow::Result<()> {
if let Some(insert) = self.insert_stmt.as_mut() {
let data = bincode::serialize(&data)?;
insert.bind((1, &self.cache.module_hash[..]))?;
insert.bind((2, key))?;
insert.bind((3, &data[..]))?;
while insert.next()? == sqlite::State::Row {}
insert.reset()?;
}
Ok(())
}
}