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