Skip to main content

reifydb_core/window/
state.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{collections::HashMap, hash::Hash, mem, sync::Arc};
5
6use reifydb_codec::key::encoded::{EncodedKey, IntoEncodedKey};
7use reifydb_value::Result;
8use serde::{Serialize, de::DeserializeOwned};
9
10use crate::{util::lru::slab::SlabLru, window::store::WindowStore};
11
12#[derive(Clone, Copy, Debug)]
13pub enum StateBackend {
14	Data,
15
16	Internal,
17}
18
19pub struct StateCache<K, V> {
20	cache: SlabLru<K, Arc<V>>,
21	dirty: HashMap<K, Option<Arc<V>>>,
22	backend: StateBackend,
23}
24
25impl<K, V> StateCache<K, V>
26where
27	K: Hash + Eq + Clone,
28	for<'a> &'a K: IntoEncodedKey,
29	V: Clone + Serialize + DeserializeOwned,
30{
31	pub fn new(capacity: usize) -> Self {
32		Self::with_backend(capacity, StateBackend::Data)
33	}
34
35	pub fn new_internal(capacity: usize) -> Self {
36		Self::with_backend(capacity, StateBackend::Internal)
37	}
38
39	fn with_backend(capacity: usize, backend: StateBackend) -> Self {
40		Self {
41			cache: SlabLru::new(capacity),
42			dirty: HashMap::new(),
43			backend,
44		}
45	}
46
47	pub fn get_arc(&mut self, store: &mut impl WindowStore, key: &K) -> Result<Option<Arc<V>>> {
48		if let Some(cached) = self.cache.get(key) {
49			return Ok(Some(cached));
50		}
51
52		if let Some(slot) = self.dirty.get(key) {
53			return Ok(slot.clone());
54		}
55
56		let encoded_key = key.into_encoded_key();
57		let loaded = match self.backend {
58			StateBackend::Data => store.state_get::<V>(&encoded_key)?,
59			StateBackend::Internal => store.internal_get::<V>(&encoded_key)?,
60		};
61		match loaded {
62			Some(value) => {
63				let arc = Arc::new(value);
64				self.cache.put(key.clone(), arc.clone());
65				Ok(Some(arc))
66			}
67			None => Ok(None),
68		}
69	}
70
71	pub fn get(&mut self, store: &mut impl WindowStore, key: &K) -> Result<Option<V>> {
72		Ok(self.get_arc(store, key)?.map(|arc| (*arc).clone()))
73	}
74
75	pub fn warm(&mut self, store: &mut impl WindowStore, keys: &[K]) -> Result<()> {
76		let mut to_load: Vec<K> = Vec::new();
77		for key in keys {
78			if self.cache.contains_key(key) || self.dirty.contains_key(key) {
79				continue;
80			}
81			to_load.push(key.clone());
82		}
83		if to_load.is_empty() {
84			return Ok(());
85		}
86
87		let mut by_encoded: HashMap<Vec<u8>, K> = HashMap::with_capacity(to_load.len());
88		let mut encoded_keys: Vec<EncodedKey> = Vec::with_capacity(to_load.len());
89		for key in &to_load {
90			let encoded = key.into_encoded_key();
91			by_encoded.insert(encoded.as_bytes().to_vec(), key.clone());
92			encoded_keys.push(encoded);
93		}
94
95		let cache = &mut self.cache;
96		let mut visit = |encoded: EncodedKey, value: V| -> Result<()> {
97			if let Some(key) = by_encoded.get(encoded.as_bytes()) {
98				cache.put(key.clone(), Arc::new(value));
99			}
100			Ok(())
101		};
102		match self.backend {
103			StateBackend::Data => store.state_get_many_visit::<V>(&encoded_keys, &mut visit)?,
104			StateBackend::Internal => store.internal_get_many_visit::<V>(&encoded_keys, &mut visit)?,
105		}
106		Ok(())
107	}
108
109	pub fn set(&mut self, _store: &mut impl WindowStore, key: &K, value: &V) -> Result<()> {
110		let arc = Arc::new(value.clone());
111		self.cache.put(key.clone(), arc.clone());
112		self.dirty.insert(key.clone(), Some(arc));
113		Ok(())
114	}
115
116	pub fn put(&mut self, _store: &mut impl WindowStore, key: &K, value: V) -> Result<()> {
117		let arc = Arc::new(value);
118		self.cache.put(key.clone(), arc.clone());
119		self.dirty.insert(key.clone(), Some(arc));
120		Ok(())
121	}
122
123	pub fn put_arc(&mut self, _store: &mut impl WindowStore, key: &K, value: Arc<V>) -> Result<()> {
124		self.cache.put(key.clone(), value.clone());
125		self.dirty.insert(key.clone(), Some(value));
126		Ok(())
127	}
128
129	pub fn modify<F>(&mut self, store: &mut impl WindowStore, key: &K, f: F) -> Result<()>
130	where
131		F: FnOnce(&mut V) -> Result<()>,
132		V: Default,
133	{
134		let mut arc = self.get_arc(store, key)?.unwrap_or_else(|| Arc::new(V::default()));
135		f(Arc::make_mut(&mut arc))?;
136		self.put_arc(store, key, arc)
137	}
138
139	pub fn remove(&mut self, _store: &mut impl WindowStore, key: &K) -> Result<()> {
140		self.cache.remove(key);
141		self.dirty.insert(key.clone(), None);
142		Ok(())
143	}
144
145	pub fn flush(&mut self, store: &mut impl WindowStore) -> Result<()> {
146		let dirty = mem::take(&mut self.dirty);
147		for (key, slot) in dirty {
148			let encoded_key = (&key).into_encoded_key();
149			match (slot, self.backend) {
150				(Some(value), StateBackend::Data) => store.state_set(&encoded_key, value.as_ref())?,
151				(Some(value), StateBackend::Internal) => {
152					store.internal_set(&encoded_key, value.as_ref())?
153				}
154				(None, StateBackend::Data) => store.state_drop(&encoded_key)?,
155				(None, StateBackend::Internal) => store.internal_drop(&encoded_key)?,
156			}
157		}
158		Ok(())
159	}
160
161	pub fn clear_cache(&mut self) {
162		self.cache.clear();
163	}
164
165	pub fn invalidate(&mut self, key: &K) {
166		self.cache.remove(key);
167	}
168
169	pub fn is_cached(&self, key: &K) -> bool {
170		self.cache.contains_key(key)
171	}
172
173	pub fn len(&self) -> usize {
174		self.cache.len()
175	}
176
177	pub fn is_empty(&self) -> bool {
178		self.cache.is_empty()
179	}
180
181	pub fn capacity(&self) -> usize {
182		self.cache.capacity()
183	}
184}
185
186impl<K, V> StateCache<K, V>
187where
188	K: Hash + Eq + Clone,
189	for<'a> &'a K: IntoEncodedKey,
190	V: Clone + Default + Serialize + DeserializeOwned,
191{
192	pub fn get_or_default(&mut self, store: &mut impl WindowStore, key: &K) -> Result<V> {
193		match self.get(store, key)? {
194			Some(value) => Ok(value),
195			None => Ok(V::default()),
196		}
197	}
198
199	pub fn update<U>(&mut self, store: &mut impl WindowStore, key: &K, updater: U) -> Result<V>
200	where
201		U: FnOnce(&mut V) -> Result<()>,
202	{
203		let mut value = self.get_or_default(store, key)?;
204		updater(&mut value)?;
205		self.set(store, key, &value)?;
206		Ok(value)
207	}
208}
209
210#[cfg(test)]
211mod tests {
212	use std::{collections::HashMap, ops::Bound};
213
214	use postcard::{from_bytes, to_allocvec};
215	use reifydb_codec::key::encoded::EncodedKeyRange;
216	use reifydb_value::value::row_number::RowNumber;
217
218	use super::*;
219
220	#[derive(Default)]
221	struct MockStore {
222		data: HashMap<Vec<u8>, Vec<u8>>,
223		internal: HashMap<Vec<u8>, Vec<u8>>,
224	}
225
226	impl WindowStore for MockStore {
227		fn state_get<V: DeserializeOwned>(&mut self, key: &EncodedKey) -> Result<Option<V>> {
228			Ok(self.data.get(key.as_bytes()).map(|b| from_bytes(b).expect("decode")))
229		}
230		fn state_get_many_visit<V: DeserializeOwned>(
231			&mut self,
232			keys: &[EncodedKey],
233			visit: &mut dyn FnMut(EncodedKey, V) -> Result<()>,
234		) -> Result<()> {
235			for key in keys {
236				if let Some(b) = self.data.get(key.as_bytes()) {
237					visit(key.clone(), from_bytes(b).expect("decode"))?;
238				}
239			}
240			Ok(())
241		}
242		fn state_set<V: Serialize>(&mut self, key: &EncodedKey, value: &V) -> Result<()> {
243			self.data.insert(key.as_bytes().to_vec(), to_allocvec(value).expect("encode"));
244			Ok(())
245		}
246		fn state_remove(&mut self, key: &EncodedKey) -> Result<()> {
247			self.data.remove(key.as_bytes());
248			Ok(())
249		}
250		fn state_drop(&mut self, key: &EncodedKey) -> Result<()> {
251			self.data.remove(key.as_bytes());
252			Ok(())
253		}
254		fn internal_get<V: DeserializeOwned>(&mut self, key: &EncodedKey) -> Result<Option<V>> {
255			Ok(self.internal.get(key.as_bytes()).map(|b| from_bytes(b).expect("decode")))
256		}
257		fn internal_get_many_visit<V: DeserializeOwned>(
258			&mut self,
259			keys: &[EncodedKey],
260			visit: &mut dyn FnMut(EncodedKey, V) -> Result<()>,
261		) -> Result<()> {
262			for key in keys {
263				if let Some(b) = self.internal.get(key.as_bytes()) {
264					visit(key.clone(), from_bytes(b).expect("decode"))?;
265				}
266			}
267			Ok(())
268		}
269		fn internal_set<V: Serialize>(&mut self, key: &EncodedKey, value: &V) -> Result<()> {
270			self.internal.insert(key.as_bytes().to_vec(), to_allocvec(value).expect("encode"));
271			Ok(())
272		}
273		fn internal_remove(&mut self, key: &EncodedKey) -> Result<()> {
274			self.internal.remove(key.as_bytes());
275			Ok(())
276		}
277		fn internal_drop(&mut self, key: &EncodedKey) -> Result<()> {
278			self.internal.remove(key.as_bytes());
279			Ok(())
280		}
281		fn internal_range_visit<V: DeserializeOwned>(
282			&mut self,
283			range: EncodedKeyRange,
284			visit: &mut dyn FnMut(EncodedKey, V) -> Result<()>,
285		) -> Result<()> {
286			let after_start = |k: &[u8]| match &range.start {
287				Bound::Included(s) => k >= s.as_bytes(),
288				Bound::Excluded(s) => k > s.as_bytes(),
289				Bound::Unbounded => true,
290			};
291			let before_end = |k: &[u8]| match &range.end {
292				Bound::Included(e) => k <= e.as_bytes(),
293				Bound::Excluded(e) => k < e.as_bytes(),
294				Bound::Unbounded => true,
295			};
296			let mut matched: Vec<(Vec<u8>, Vec<u8>)> = self
297				.internal
298				.iter()
299				.filter(|(k, _)| after_start(k) && before_end(k))
300				.map(|(k, v)| (k.clone(), v.clone()))
301				.collect();
302			matched.sort_by(|a, b| a.0.cmp(&b.0));
303			for (k, b) in matched {
304				visit(EncodedKey::new(k), from_bytes(&b).expect("decode"))?;
305			}
306			Ok(())
307		}
308		fn get_or_create_row_number(&mut self, _key: &EncodedKey) -> Result<(RowNumber, bool)> {
309			Ok((RowNumber(1), true))
310		}
311		fn get_or_create_row_numbers(&mut self, keys: &[EncodedKey]) -> Result<Vec<(RowNumber, bool)>> {
312			Ok(keys.iter().enumerate().map(|(i, _)| (RowNumber(i as u64 + 1), true)).collect())
313		}
314		fn allocate_row_numbers(&mut self, _count: u64) -> Result<RowNumber> {
315			Ok(RowNumber(1))
316		}
317		fn clock_now_nanos(&self) -> u64 {
318			0
319		}
320	}
321
322	#[test]
323	fn set_then_flush_persists_to_store_and_survives_cache_clear() {
324		let mut store = MockStore::default();
325		let mut cache: StateCache<String, i32> = StateCache::new(100);
326
327		// A set is buffered (dirty) and visible via get before flush.
328		cache.set(&mut store, &"a".to_string(), &7).unwrap();
329		assert_eq!(cache.get(&mut store, &"a".to_string()).unwrap(), Some(7));
330		// Nothing reached the backing store yet.
331		assert!(store.data.is_empty());
332
333		cache.flush(&mut store).unwrap();
334		assert!(!store.data.is_empty(), "flush must write dirty entries to the store");
335
336		// After dropping the in-memory cache, the value must load from the store.
337		cache.clear_cache();
338		assert_eq!(cache.get(&mut store, &"a".to_string()).unwrap(), Some(7));
339	}
340
341	#[test]
342	fn warm_bulk_loads_present_keys_and_skips_absent() {
343		let mut store = MockStore::default();
344		{
345			let mut seed: StateCache<String, i32> = StateCache::new(100);
346			seed.set(&mut store, &"a".to_string(), &1).unwrap();
347			seed.set(&mut store, &"b".to_string(), &2).unwrap();
348			seed.flush(&mut store).unwrap();
349		}
350
351		let mut cache: StateCache<String, i32> = StateCache::new(100);
352		let keys = vec!["a".to_string(), "b".to_string(), "missing".to_string()];
353		cache.warm(&mut store, &keys).unwrap();
354
355		assert!(cache.is_cached(&"a".to_string()));
356		assert!(cache.is_cached(&"b".to_string()));
357		assert!(!cache.is_cached(&"missing".to_string()));
358	}
359
360	#[test]
361	fn dirty_write_shadows_committed_value_during_warm() {
362		let mut store = MockStore::default();
363		{
364			let mut seed: StateCache<String, i32> = StateCache::new(100);
365			seed.set(&mut store, &"a".to_string(), &1).unwrap();
366			seed.flush(&mut store).unwrap();
367		}
368
369		let mut cache: StateCache<String, i32> = StateCache::new(100);
370		cache.set(&mut store, &"a".to_string(), &99).unwrap();
371		cache.warm(&mut store, &["a".to_string()]).unwrap();
372		assert_eq!(
373			cache.get(&mut store, &"a".to_string()).unwrap(),
374			Some(99),
375			"pending write must shadow store"
376		);
377	}
378
379	#[test]
380	fn internal_backend_round_trips_through_internal_store() {
381		let mut store = MockStore::default();
382		let mut cache: StateCache<String, i32> = StateCache::new_internal(100);
383		cache.set(&mut store, &"a".to_string(), &5).unwrap();
384		cache.flush(&mut store).unwrap();
385		assert!(store.data.is_empty(), "internal backend must not write to the data store");
386		assert!(!store.internal.is_empty(), "internal backend must write to the internal store");
387		cache.clear_cache();
388		assert_eq!(cache.get(&mut store, &"a".to_string()).unwrap(), Some(5));
389	}
390}