use std::future::Future;
use std::hash::Hash;
use std::sync::Arc;
use anyhow::{Error, Result};
use quick_cache::{Equivalent, Weighter};
use surrealism_runtime::runtime::Runtime;
use crate::catalog::{DatabaseId, NamespaceId};
#[cfg(feature = "http")]
use crate::http::HttpClient;
pub struct SurrealismCache {
cache: quick_cache::sync::Cache<SurrealismCacheKey, SurrealismCacheValue, Weight>,
}
impl SurrealismCache {
pub fn new(size: usize) -> Self {
Self {
cache: quick_cache::sync::Cache::with_weighter(size, size as u64, Weight),
}
}
pub fn remove(&self, lookup: &SurrealismCacheLookup) {
self.cache.remove(lookup);
}
pub async fn get_or_insert_with<F, Fut>(
&self,
lookup: &SurrealismCacheLookup<'_>,
compute: F,
) -> Result<SurrealismCachedModule>
where
F: FnOnce() -> Fut,
Fut: Future<Output = Result<SurrealismCachedModule>>,
{
let value = match self.cache.get(lookup) {
Some(cached) => cached,
None => {
let compute = async {
let value = compute().await?;
let wrapped = SurrealismCacheValue {
runtime: value.runtime,
module_display_name: value.module_display_name,
#[cfg(feature = "http")]
client: value.client,
};
Result::<_, Error>::Ok(wrapped)
};
self.cache.get_or_insert_async(&lookup.to_key(), compute).await?
}
};
Ok(SurrealismCachedModule {
runtime: value.runtime,
module_display_name: value.module_display_name,
#[cfg(feature = "http")]
client: value.client,
})
}
}
#[derive(Clone, Hash, Eq, PartialEq)]
pub enum SurrealismCacheKey {
File(NamespaceId, DatabaseId, String, String),
Silo(NamespaceId, DatabaseId, String, String, u32, u32, u32),
}
#[derive(Hash, Eq, PartialEq, Debug)]
pub enum SurrealismCacheLookup<'a> {
File(&'a NamespaceId, &'a DatabaseId, &'a str, &'a str),
Silo(&'a NamespaceId, &'a DatabaseId, &'a str, &'a str, u32, u32, u32),
}
impl SurrealismCacheLookup<'_> {
pub fn to_key(&self) -> SurrealismCacheKey {
match self {
SurrealismCacheLookup::File(ns, db, bucket, key) => {
SurrealismCacheKey::File(**ns, **db, (*bucket).to_string(), (*key).to_string())
}
SurrealismCacheLookup::Silo(ns, db, org, pkg, maj, min, pat) => {
SurrealismCacheKey::Silo(
**ns,
**db,
(*org).to_string(),
(*pkg).to_string(),
*maj,
*min,
*pat,
)
}
}
}
}
impl<'a> From<SurrealismCacheLookup<'a>> for SurrealismCacheKey {
fn from(lookup: SurrealismCacheLookup<'a>) -> Self {
lookup.to_key()
}
}
impl Equivalent<SurrealismCacheKey> for SurrealismCacheLookup<'_> {
fn equivalent(&self, key: &SurrealismCacheKey) -> bool {
match (self, key) {
(Self::File(a1, b1, c1, d1), SurrealismCacheKey::File(a2, b2, c2, d2)) => {
a1.0 == a2.0 && b1.0 == b2.0 && c1 == c2 && d1 == d2
}
(
Self::Silo(a1, b1, c1, d1, e1, f1, g1),
SurrealismCacheKey::Silo(a2, b2, c2, d2, e2, f2, g2),
) => {
a1.0 == a2.0
&& b1.0 == b2.0 && c1 == c2
&& d1 == d2 && e1 == e2
&& f1 == f2 && g1 == g2
}
_ => false,
}
}
}
#[derive(Clone)]
pub(crate) struct SurrealismCachedModule {
pub runtime: Arc<Runtime>,
pub module_display_name: Arc<str>,
#[cfg(feature = "http")]
pub client: Arc<HttpClient>,
}
#[derive(Clone)]
pub struct SurrealismCacheValue {
pub(crate) runtime: Arc<Runtime>,
pub(crate) module_display_name: Arc<str>,
#[cfg(feature = "http")]
pub(crate) client: Arc<HttpClient>,
}
#[derive(Clone)]
pub(crate) struct Weight;
impl Weighter<SurrealismCacheKey, SurrealismCacheValue> for Weight {
fn weight(&self, _key: &SurrealismCacheKey, _val: &SurrealismCacheValue) -> u64 {
1
}
}
#[cfg(test)]
mod tests {
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
use super::*;
fn hash_of<T: Hash>(value: &T) -> u64 {
let mut hasher = DefaultHasher::new();
value.hash(&mut hasher);
hasher.finish()
}
#[test]
fn silo_entries_are_scoped_per_database() {
let ns = NamespaceId(1);
let db = DatabaseId(1);
let other_ns = NamespaceId(2);
let other_db = DatabaseId(2);
let base = SurrealismCacheLookup::Silo(&ns, &db, "org", "pkg", 1, 2, 3);
let key = base.to_key();
assert!(base.equivalent(&key));
assert!(
!SurrealismCacheLookup::Silo(&other_ns, &db, "org", "pkg", 1, 2, 3).equivalent(&key)
);
assert!(
!SurrealismCacheLookup::Silo(&ns, &other_db, "org", "pkg", 1, 2, 3).equivalent(&key)
);
assert!(!SurrealismCacheLookup::Silo(&ns, &db, "org", "pkg", 1, 2, 4).equivalent(&key));
}
#[test]
fn lookup_and_key_hash_identically() {
let ns = NamespaceId(7);
let db = DatabaseId(9);
let silo = SurrealismCacheLookup::Silo(&ns, &db, "org", "pkg", 1, 2, 3);
assert_eq!(hash_of(&silo), hash_of(&silo.to_key()));
let file = SurrealismCacheLookup::File(&ns, &db, "bucket", "key");
assert_eq!(hash_of(&file), hash_of(&file.to_key()));
}
}