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