1mod cache;
8#[cfg(feature = "store-crypto")]
9mod encrypted;
10#[cfg(feature = "file")]
11mod file;
12#[cfg(feature = "redis")]
13mod redis;
14#[cfg(feature = "sql")]
15mod sql;
16
17use bytes::Bytes;
18use std::collections::HashMap;
19use std::collections::hash_map::DefaultHasher;
20use std::future::Future;
21use std::hash::{Hash, Hasher};
22use std::pin::Pin;
23use std::sync::Arc;
24use std::time::{Duration, Instant};
25use tokio::sync::Mutex;
26
27pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
28
29pub use cache::{Cache, CacheError};
30
31pub trait KvStore: Send + Sync + 'static {
32 fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<Bytes>>;
33 fn set<'a>(&'a self, key: &'a str, val: Bytes, ttl: Option<Duration>) -> BoxFuture<'a, ()>;
34 fn remove<'a>(&'a self, key: &'a str) -> BoxFuture<'a, ()>;
35 fn incr<'a>(&'a self, key: &'a str, by: i64, ttl: Option<Duration>) -> BoxFuture<'a, u64>;
37 fn clear_prefix<'a>(&'a self, prefix: &'a str) -> BoxFuture<'a, u64>;
39}
40
41pub fn namespace(store: Arc<dyn KvStore>, name: &str) -> Namespace {
42 Namespace {
43 store,
44 prefix: format!("{name}:"),
45 }
46}
47
48#[derive(Clone)]
51pub struct AppStore {
52 pub inner: Arc<dyn KvStore>,
53}
54
55impl AppStore {
56 pub fn new(store: Arc<dyn KvStore>) -> Self {
57 Self { inner: store }
58 }
59
60 pub fn memory() -> Self {
61 Self::new(Arc::new(MemoryStore::new()))
62 }
63
64 pub fn namespaced(&self, name: &str) -> Arc<dyn KvStore> {
65 Arc::new(namespace(Arc::clone(&self.inner), name))
66 }
67}
68
69#[cfg(feature = "store-crypto")]
70pub use encrypted::{encrypted, encrypted_ns, AppKey, Encrypted};
71
72#[cfg(feature = "file")]
73pub use file::{Durability, FileStore};
74
75#[cfg(feature = "redis")]
76pub use redis::RedisStore;
77
78#[cfg(feature = "sql")]
79pub use sql::SqlStore;
80
81#[derive(Clone)]
83pub struct Namespace {
84 store: Arc<dyn KvStore>,
85 prefix: String,
86}
87
88impl Namespace {
89 fn full(&self, key: &str) -> String {
90 format!("{}{key}", self.prefix)
91 }
92}
93
94impl KvStore for Namespace {
95 fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<Bytes>> {
96 let k = self.full(key);
97 Box::pin(async move { self.store.get(&k).await })
98 }
99
100 fn set<'a>(&'a self, key: &'a str, val: Bytes, ttl: Option<Duration>) -> BoxFuture<'a, ()> {
101 let k = self.full(key);
102 Box::pin(async move { self.store.set(&k, val, ttl).await })
103 }
104
105 fn remove<'a>(&'a self, key: &'a str) -> BoxFuture<'a, ()> {
106 let k = self.full(key);
107 Box::pin(async move { self.store.remove(&k).await })
108 }
109
110 fn incr<'a>(&'a self, key: &'a str, by: i64, ttl: Option<Duration>) -> BoxFuture<'a, u64> {
111 let k = self.full(key);
112 Box::pin(async move { self.store.incr(&k, by, ttl).await })
113 }
114
115 fn clear_prefix<'a>(&'a self, prefix: &'a str) -> BoxFuture<'a, u64> {
116 let p = self.full(prefix);
117 Box::pin(async move { self.store.clear_prefix(&p).await })
118 }
119}
120
121struct Entry {
122 val: Bytes,
123 exp: Option<Instant>,
124}
125
126type ShardMap = Arc<Mutex<HashMap<String, Entry>>>;
127
128#[derive(Clone)]
130pub struct MemoryStore {
131 shards: Arc<[ShardMap]>,
132}
133
134impl Default for MemoryStore {
135 fn default() -> Self {
136 Self::new()
137 }
138}
139
140impl MemoryStore {
141 pub fn new() -> Self {
143 let n = std::thread::available_parallelism()
144 .map(|p| p.get() * 2)
145 .unwrap_or(2)
146 .max(1);
147 Self::with_shards(n)
148 }
149
150 pub fn with_shards(n: usize) -> Self {
152 let n = n.max(1);
153 let shards: Vec<_> = (0..n)
154 .map(|_| Arc::new(Mutex::new(HashMap::new())))
155 .collect();
156 Self {
157 shards: shards.into(),
158 }
159 }
160
161 fn shard(&self, key: &str) -> &ShardMap {
162 &self.shards[shard_index(key, self.shards.len())]
163 }
164
165 fn alive(e: &Entry, now: Instant) -> bool {
166 e.exp.map(|t| t > now).unwrap_or(true)
167 }
168}
169
170fn shard_index(key: &str, n: usize) -> usize {
171 let mut h = DefaultHasher::new();
172 key.hash(&mut h);
173 (h.finish() as usize) % n
174}
175
176impl KvStore for MemoryStore {
177 fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<Bytes>> {
178 let shard = Arc::clone(self.shard(key));
179 let key = key.to_string();
180 Box::pin(async move {
181 let mut map = shard.lock().await;
182 let now = Instant::now();
183 match map.get(&key) {
184 Some(e) if Self::alive(e, now) => Some(e.val.clone()),
185 Some(_) => {
186 map.remove(&key);
187 None
188 }
189 None => None,
190 }
191 })
192 }
193
194 fn set<'a>(&'a self, key: &'a str, val: Bytes, ttl: Option<Duration>) -> BoxFuture<'a, ()> {
195 let shard = Arc::clone(self.shard(key));
196 let key = key.to_string();
197 Box::pin(async move {
198 let mut map = shard.lock().await;
199 map.insert(
200 key,
201 Entry {
202 val,
203 exp: ttl.map(|d| Instant::now() + d),
204 },
205 );
206 })
207 }
208
209 fn remove<'a>(&'a self, key: &'a str) -> BoxFuture<'a, ()> {
210 let shard = Arc::clone(self.shard(key));
211 let key = key.to_string();
212 Box::pin(async move {
213 shard.lock().await.remove(&key);
214 })
215 }
216
217 fn incr<'a>(&'a self, key: &'a str, by: i64, ttl: Option<Duration>) -> BoxFuture<'a, u64> {
218 let shard = Arc::clone(self.shard(key));
219 let key = key.to_string();
220 Box::pin(async move {
221 let mut map = shard.lock().await;
222 let now = Instant::now();
223 let cur = match map.get(&key) {
224 Some(e) if Self::alive(e, now) => {
225 let s = std::str::from_utf8(&e.val).unwrap_or("0");
226 s.parse::<i64>().unwrap_or(0)
227 }
228 _ => 0,
229 };
230 let next = (cur + by).max(0) as u64;
231 map.insert(
232 key,
233 Entry {
234 val: Bytes::from(next.to_string()),
235 exp: ttl.map(|d| now + d),
236 },
237 );
238 next
239 })
240 }
241
242 fn clear_prefix<'a>(&'a self, prefix: &'a str) -> BoxFuture<'a, u64> {
243 let prefix = prefix.to_string();
244 let shards: Vec<_> = self.shards.iter().cloned().collect();
245 Box::pin(async move {
246 let mut total = 0u64;
247 for shard in shards {
248 let mut map = shard.lock().await;
249 let keys: Vec<_> = map
250 .keys()
251 .filter(|k| k.starts_with(&prefix))
252 .cloned()
253 .collect();
254 total += keys.len() as u64;
255 for k in keys {
256 map.remove(&k);
257 }
258 }
259 total
260 })
261 }
262}
263
264pub mod conformance {
266 use super::*;
267 use std::sync::Arc;
268
269 pub async fn run(store: Arc<dyn KvStore>) {
270 get_set_ttl(store.clone()).await;
271 namespace_isolation(store.clone()).await;
272 incr_atomic(store.clone()).await;
273 clear_prefix_scoped(store).await;
274 }
275
276 async fn get_set_ttl(store: Arc<dyn KvStore>) {
277 store
278 .set("a", Bytes::from_static(b"1"), Some(Duration::from_millis(50)))
279 .await;
280 assert_eq!(store.get("a").await.as_deref(), Some(b"1".as_slice()));
281 tokio::time::sleep(Duration::from_millis(80)).await;
282 assert!(store.get("a").await.is_none());
283 }
284
285 async fn namespace_isolation(store: Arc<dyn KvStore>) {
286 let a = namespace(store.clone(), "a");
287 let b = namespace(store.clone(), "b");
288 a.set("k", Bytes::from_static(b"A"), None).await;
289 b.set("k", Bytes::from_static(b"B"), None).await;
290 assert_eq!(a.get("k").await.as_deref(), Some(b"A".as_slice()));
291 assert_eq!(b.get("k").await.as_deref(), Some(b"B".as_slice()));
292 a.clear_prefix("").await;
293 assert!(a.get("k").await.is_none());
294 assert_eq!(b.get("k").await.as_deref(), Some(b"B".as_slice()));
295 }
296
297 async fn incr_atomic(store: Arc<dyn KvStore>) {
298 store.remove("c").await;
299 let mut handles = Vec::new();
300 for _ in 0..50 {
301 let s = store.clone();
302 handles.push(tokio::spawn(async move {
303 s.incr("c", 1, None).await;
304 }));
305 }
306 for h in handles {
307 h.await.unwrap();
308 }
309 assert_eq!(store.get("c").await.unwrap().as_ref(), b"50");
310 }
311
312 async fn clear_prefix_scoped(store: Arc<dyn KvStore>) {
313 store.set("p:1", Bytes::from_static(b"x"), None).await;
314 store.set("p:2", Bytes::from_static(b"y"), None).await;
315 store.set("q:1", Bytes::from_static(b"z"), None).await;
316 assert_eq!(store.clear_prefix("p:").await, 2);
317 assert!(store.get("p:1").await.is_none());
318 assert_eq!(store.get("q:1").await.as_deref(), Some(b"z".as_slice()));
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
327 async fn memory_conformance() {
328 conformance::run(Arc::new(MemoryStore::new())).await;
329 }
330
331 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
332 async fn sharded_clear_prefix_across_shards() {
333 let store = MemoryStore::with_shards(4);
334 for i in 0..32 {
335 store
336 .set(
337 &format!("shard:{i}"),
338 Bytes::from_static(b"v"),
339 None,
340 )
341 .await;
342 }
343 store
344 .set("other:1", Bytes::from_static(b"z"), None)
345 .await;
346 assert_eq!(store.clear_prefix("shard:").await, 32);
347 assert!(store.get("shard:0").await.is_none());
348 assert_eq!(
349 store.get("other:1").await.as_deref(),
350 Some(b"z".as_slice())
351 );
352 }
353
354 #[tokio::test]
355 async fn cache_remember_memory() {
356 let store = AppStore::memory();
357 let cache = store.cache();
358 let v = cache
359 .remember("k", None, || async { Ok::<_, CacheError>(42u32) })
360 .await
361 .unwrap();
362 assert_eq!(v, 42);
363 let hit = cache.get_json::<u32>("k").await;
364 assert_eq!(hit, Some(42));
365 }
366}