1use std::{any::Any, collections::BTreeMap};
14
15use async_trait::async_trait;
16use bytes::{BufMut, Bytes};
17use krabka_units::prelude::*;
18
19use crate::{
20 processor::serde::Serde,
21 store::{
22 api::StateStore,
23 snapshot::{SnapshotReader, put_sized, store_payload},
24 },
25};
26
27#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct VersionedRecord<V> {
34 pub value: V,
35 pub valid_from: i64,
36 pub valid_to: Option<i64>,
37}
38
39#[async_trait]
41pub trait VersionedKeyValueStore<K: Send + Sync, V: Send>: StateStore {
42 async fn put(&mut self, key: K, value: Option<V>, timestamp: i64);
49 async fn delete(&mut self, key: &K, timestamp: i64) -> Option<VersionedRecord<V>>;
54 async fn get(&self, key: &K) -> Option<VersionedRecord<V>>;
59 async fn get_as_of(&self, key: &K, as_of: i64) -> Option<VersionedRecord<V>>;
64}
65
66pub struct VersionedBytesStore<K, V> {
67 name: String,
68 changelog_topic: String,
69 history_retention: Time,
70 key_serde: Box<dyn Serde<K>>,
71 value_serde: Box<dyn Serde<V>>,
72 chains: BTreeMap<Bytes, BTreeMap<i64, Option<Bytes>>>,
74 observed_stream_time: i64,
75 changelog: Vec<(Bytes, Option<Bytes>, i64)>,
77 logging: bool,
78}
79
80impl<K: 'static, V: 'static> VersionedBytesStore<K, V> {
81 #[must_use]
82 pub(crate) fn new(
83 name: String,
84 history_retention: Time,
85 key_serde: Box<dyn Serde<K>>,
86 value_serde: Box<dyn Serde<V>>,
87 changelog_topic: String,
88 ) -> Self {
89 Self {
90 name,
91 changelog_topic,
92 history_retention,
93 key_serde,
94 value_serde,
95 chains: BTreeMap::new(),
96 observed_stream_time: i64::MIN,
97 changelog: Vec::new(),
98 logging: true,
99 }
100 }
101
102 #[must_use]
103 pub fn in_memory(
104 name: String,
105 history_retention: Time,
106 key_serde: Box<dyn Serde<K>>,
107 value_serde: Box<dyn Serde<V>>,
108 changelog_topic: String,
109 ) -> Self {
110 Self::new(
111 name,
112 history_retention,
113 key_serde,
114 value_serde,
115 changelog_topic,
116 )
117 }
118
119 fn horizon(&self) -> i64 {
124 self.observed_stream_time
127 .saturating_sub(self.history_retention.millis_i64())
128 }
129
130 fn insert_raw(&mut self, key: Bytes, valid_from: i64, value: Option<Bytes>) -> bool {
135 self.observed_stream_time = self.observed_stream_time.max(valid_from);
136 let h = self.horizon();
138 if let Some(chain) = self.chains.get(&key)
143 && let Some(next_above) = chain.range((valid_from + 1)..).next().map(|(t, _)| *t)
144 && next_above <= h
145 {
146 return false;
147 }
148 let chain = self.chains.entry(key).or_default();
149 chain.insert(valid_from, value);
150 let times: Vec<i64> = chain.keys().copied().collect();
154 for w in times.windows(2) {
155 let (from, next) = (w[0], w[1]);
156 if next <= h {
157 chain.remove(&from);
158 }
159 }
160 true
161 }
162}
163
164#[async_trait]
165impl<K: Send + 'static, V: Send + 'static> StateStore for VersionedBytesStore<K, V> {
166 fn name(&self) -> &str {
167 &self.name
168 }
169 async fn flush(&mut self) {}
170 fn close(&mut self) {}
171 fn as_any_mut(&mut self) -> &mut dyn Any {
172 self
173 }
174 fn changelog_topic(&self) -> &str {
175 &self.changelog_topic
176 }
177 fn take_changelog(&mut self) -> Vec<(Bytes, Option<Bytes>)> {
178 self.changelog.drain(..).map(|(k, v, _ts)| (k, v)).collect()
179 }
180 fn take_changelog_ts(&mut self) -> Vec<(Bytes, Option<Bytes>, Option<i64>)> {
181 self.changelog
182 .drain(..)
183 .map(|(k, v, ts)| (k, v, Some(ts)))
184 .collect()
185 }
186 async fn apply_changelog(&mut self, _key: Bytes, _value: Option<Bytes>) {
187 }
191 async fn apply_changelog_ts(&mut self, key: Bytes, value: Option<Bytes>, timestamp: i64) {
192 self.insert_raw(key, timestamp, value);
193 }
194 fn set_logging(&mut self, on: bool) {
195 self.logging = on;
196 }
197 fn as_iq(&self) -> Option<&dyn crate::store::iq::IqQueryable> {
198 Some(self)
199 }
200 async fn clear(&mut self) {
201 self.chains.clear();
202 self.changelog.clear();
203 self.observed_stream_time = i64::MIN;
204 }
205 async fn snapshot(&mut self) -> Bytes {
206 let mut buffer = store_payload();
212 buffer.put_i64(self.observed_stream_time);
213 buffer.put_u32(u32::try_from(self.chains.len()).expect("chain count fits u32"));
214 for (key, chain) in &self.chains {
215 put_sized(&mut buffer, key);
216 buffer.put_u32(u32::try_from(chain.len()).expect("version count fits u32"));
217 for (valid_from, value) in chain {
218 buffer.put_i64(*valid_from);
219 match value {
220 Some(value) => {
221 buffer.put_u8(1);
222 put_sized(&mut buffer, value);
223 }
224 None => buffer.put_u8(0),
226 }
227 }
228 }
229 buffer.freeze()
230 }
231 async fn restore_snapshot(
232 &mut self,
233 data: Bytes,
234 ) -> Result<(), crate::error::StreamsClientError> {
235 let mut reader = SnapshotReader::new(&data);
236 reader.store_version()?;
237 let observed_stream_time = reader.i64()?;
238 let chain_count = reader.u32()?;
239 let mut chains: BTreeMap<Bytes, BTreeMap<i64, Option<Bytes>>> = BTreeMap::new();
240 for _ in 0..chain_count {
241 let key = reader.sized()?;
242 let version_count = reader.u32()?;
243 let mut chain = BTreeMap::new();
244 for _ in 0..version_count {
245 let valid_from = reader.i64()?;
246 let value = match reader.u8()? {
247 0 => None,
248 1 => Some(reader.sized()?),
249 tag => {
250 return Err(crate::error::StreamsClientError::Snapshot(format!(
251 "unknown versioned store value tag {tag}"
252 )));
253 }
254 };
255 chain.insert(valid_from, value);
256 }
257 chains.insert(key, chain);
258 }
259 reader.finish()?;
260 self.chains = chains;
264 self.observed_stream_time = observed_stream_time;
265 self.changelog.clear();
266 Ok(())
267 }
268}
269
270#[async_trait]
271impl<K: Send + Sync + 'static, V: Send + 'static> VersionedKeyValueStore<K, V>
272 for VersionedBytesStore<K, V>
273{
274 async fn put(&mut self, key: K, value: Option<V>, timestamp: i64) {
275 let kb = self.key_serde.serialize(&self.changelog_topic, &key);
276 let vb = value
277 .as_ref()
278 .map(|v| self.value_serde.serialize(&self.changelog_topic, v));
279 let inserted = self.insert_raw(kb.clone(), timestamp, vb.clone());
280 if inserted && self.logging {
281 self.changelog.push((kb, vb, timestamp));
284 }
285 }
286
287 async fn delete(&mut self, key: &K, timestamp: i64) -> Option<VersionedRecord<V>> {
288 let prev = self.get_as_of(key, timestamp).await;
289 let kb = self.key_serde.serialize(&self.changelog_topic, key);
290 let inserted = self.insert_raw(kb.clone(), timestamp, None);
291 if inserted && self.logging {
292 self.changelog.push((kb, None, timestamp));
294 }
295 prev
296 }
297
298 async fn get(&self, key: &K) -> Option<VersionedRecord<V>> {
299 let kb = self.key_serde.serialize(&self.changelog_topic, key);
300 let chain = self.chains.get(&kb)?;
301 let (&valid_from, value) = chain.iter().next_back()?;
302 let raw = value.as_ref()?; Some(VersionedRecord {
304 value: self
305 .value_serde
306 .deserialize(&self.changelog_topic, raw)
307 .expect("versioned value deserialize"),
308 valid_from,
309 valid_to: None,
310 })
311 }
312
313 async fn get_as_of(&self, key: &K, as_of: i64) -> Option<VersionedRecord<V>> {
314 let kb = self.key_serde.serialize(&self.changelog_topic, key);
315 let chain = self.chains.get(&kb)?;
316 let (&valid_from, value) = chain.range(..=as_of).next_back()?;
317 let raw = value.as_ref()?; let valid_to = chain.range((as_of + 1)..).next().map(|(t, _)| *t);
319 Some(VersionedRecord {
320 value: self
321 .value_serde
322 .deserialize(&self.changelog_topic, raw)
323 .expect("versioned value deserialize"),
324 valid_from,
325 valid_to,
326 })
327 }
328}
329
330#[async_trait]
332impl<K: Send + 'static, V: Send + 'static> crate::store::iq::IqQueryable
333 for VersionedBytesStore<K, V>
334{
335 fn kind(&self) -> crate::store::iq::StoreKind {
336 crate::store::iq::StoreKind::Versioned
337 }
338 async fn iq_versioned_get(&self, key: &[u8]) -> Option<(i64, Option<i64>, Bytes)> {
339 let chain = self.chains.get(key)?;
340 let (&vf, value) = chain.iter().next_back()?;
341 let raw = value.as_ref()?;
342 Some((vf, None, raw.clone()))
343 }
344 async fn iq_versioned_get_as_of(
345 &self,
346 key: &[u8],
347 as_of: i64,
348 ) -> Option<(i64, Option<i64>, Bytes)> {
349 let chain = self.chains.get(key)?;
350 let (&vf, value) = chain.range(..=as_of).next_back()?;
351 let raw = value.as_ref()?;
352 let vt = chain.range((as_of + 1)..).next().map(|(t, _)| *t);
353 Some((vf, vt, raw.clone()))
354 }
355
356 async fn iq2_execute(
357 &self,
358 query: &crate::store::iq::Iq2Query,
359 ) -> Result<Box<dyn Any + Send>, crate::store::iq::Iq2Failure> {
360 use crate::store::iq::{Iq2Failure, Iq2Query};
361 let ser = |b: &dyn Any| -> Result<Bytes, Iq2Failure> {
362 let k = b.downcast_ref::<K>().ok_or(Iq2Failure::KeyTypeMismatch)?;
363 Ok(self.key_serde.serialize(&self.changelog_topic, k))
364 };
365 let deser = |raw: &[u8]| -> V {
366 self.value_serde
367 .deserialize(&self.changelog_topic, raw)
368 .expect("iqv2 versioned value deserialize")
369 };
370 match query {
371 Iq2Query::VersionedKey { key, as_of } => {
372 let kb = ser(&**key)?;
373 let out: Option<VersionedRecord<V>> = self.chains.get(&kb).and_then(|chain| {
374 let (valid_from, value, valid_to) = match as_of {
375 None => {
377 let (&vf, value) = chain.iter().next_back()?;
378 (vf, value, None)
379 }
380 Some(t) => {
383 let (&vf, value) = chain.range(..=*t).next_back()?;
384 let vt = chain.range((*t + 1)..).next().map(|(x, _)| *x);
385 (vf, value, vt)
386 }
387 };
388 let raw = value.as_ref()?; Some(VersionedRecord {
390 value: deser(raw),
391 valid_from,
392 valid_to,
393 })
394 });
395 Ok(Box::new(out))
396 }
397 Iq2Query::MultiVersionedKey {
398 key,
399 from_ts,
400 to_ts,
401 descending,
402 } => {
403 let kb = ser(&**key)?;
404 let from = from_ts.unwrap_or(i64::MIN);
405 let to = to_ts.unwrap_or(i64::MAX);
406 let mut out: Vec<VersionedRecord<V>> = Vec::new();
407 if let Some(chain) = self.chains.get(&kb) {
408 let entries: Vec<(i64, &Option<Bytes>)> =
409 chain.iter().map(|(t, v)| (*t, v)).collect();
410 for (i, (valid_from, value)) in entries.iter().enumerate() {
411 let Some(raw) = value.as_ref() else { continue }; let valid_to = entries.get(i + 1).map(|(t, _)| *t);
413 let overlaps = *valid_from <= to && valid_to.is_none_or(|vt| vt > from);
415 if overlaps {
416 out.push(VersionedRecord {
417 value: deser(raw),
418 valid_from: *valid_from,
419 valid_to,
420 });
421 }
422 }
423 }
424 if *descending {
425 out.reverse();
426 }
427 Ok(Box::new(out))
428 }
429 _ => Err(Iq2Failure::UnknownQueryType),
430 }
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437 use crate::processor::serde::{I64Serde, StringSerde};
438
439 fn store(retention: Time) -> VersionedBytesStore<String, i64> {
440 VersionedBytesStore::in_memory(
441 "v".into(),
442 retention,
443 Box::new(StringSerde),
444 Box::new(I64Serde),
445 "app-v-changelog".into(),
446 )
447 }
448
449 #[tokio::test]
450 async fn latest_and_as_of() {
451 let mut s = store(secs(1_000));
452 s.put("k".into(), Some(10), 100).await;
453 s.put("k".into(), Some(20), 200).await;
454 assert_eq!(s.get(&"k".into()).await.map(|r| r.value), Some(20));
455 let r = s.get_as_of(&"k".into(), 150).await.unwrap();
456 assert_eq!((r.value, r.valid_from, r.valid_to), (10, 100, Some(200)));
457 assert_eq!(s.get_as_of(&"k".into(), 50).await, None);
458 }
459
460 #[tokio::test]
461 async fn out_of_order_does_not_clobber_latest() {
462 let mut s = store(secs(1_000));
463 s.put("k".into(), Some(20), 200).await;
464 s.put("k".into(), Some(10), 100).await;
465 assert_eq!(s.get(&"k".into()).await.map(|r| r.value), Some(20));
466 assert_eq!(
467 s.get_as_of(&"k".into(), 150).await.map(|r| r.value),
468 Some(10)
469 );
470 }
471
472 #[tokio::test]
473 async fn tombstone_hides_latest_but_keeps_history() {
474 let mut s = store(secs(1_000));
475 s.put("k".into(), Some(10), 100).await;
476 let prev = s.delete(&"k".into(), 200).await;
477 assert_eq!(prev.map(|r| r.value), Some(10));
478 assert_eq!(s.get(&"k".into()).await, None);
479 assert_eq!(
480 s.get_as_of(&"k".into(), 150).await.map(|r| r.value),
481 Some(10)
482 );
483 }
484
485 #[tokio::test]
486 async fn retention_drops_old_put_and_evicts_history() {
487 let mut s = store(millis(50));
488 s.put("k".into(), Some(10), 100).await;
489 s.put("k".into(), Some(20), 200).await;
490 s.put("k".into(), Some(5), 40).await;
491 let cl = s.take_changelog();
492 assert_eq!(cl.len(), 2);
493 assert_eq!(s.get_as_of(&"k".into(), 40).await, None);
494 }
495
496 #[tokio::test]
497 async fn iq2_versioned_key_and_multi() {
499 use crate::store::iq::{Iq2Failure, Iq2Query, IqQueryable, StoreKind};
500 let mut s = store(secs(1_000));
501 s.put("k".into(), Some(10), 100).await;
503 s.put("k".into(), Some(20), 200).await;
504 s.put("k".into(), Some(30), 300).await;
505 let q: &dyn IqQueryable = s.as_iq().unwrap();
506 assert_eq!(q.kind(), StoreKind::Versioned);
507
508 let latest = q
510 .iq2_execute(&Iq2Query::VersionedKey {
511 key: Box::new("k".to_string()),
512 as_of: None,
513 })
514 .await
515 .unwrap();
516 assert_eq!(
517 *latest.downcast::<Option<VersionedRecord<i64>>>().unwrap(),
518 Some(VersionedRecord {
519 value: 30,
520 valid_from: 300,
521 valid_to: None
522 })
523 );
524
525 let asof = q
527 .iq2_execute(&Iq2Query::VersionedKey {
528 key: Box::new("k".to_string()),
529 as_of: Some(250),
530 })
531 .await
532 .unwrap();
533 assert_eq!(
534 *asof.downcast::<Option<VersionedRecord<i64>>>().unwrap(),
535 Some(VersionedRecord {
536 value: 20,
537 valid_from: 200,
538 valid_to: Some(300)
539 })
540 );
541
542 let miss = q
544 .iq2_execute(&Iq2Query::VersionedKey {
545 key: Box::new("k".to_string()),
546 as_of: Some(50),
547 })
548 .await
549 .unwrap();
550 assert_eq!(
551 *miss.downcast::<Option<VersionedRecord<i64>>>().unwrap(),
552 None
553 );
554
555 let all = q
557 .iq2_execute(&Iq2Query::MultiVersionedKey {
558 key: Box::new("k".to_string()),
559 from_ts: None,
560 to_ts: None,
561 descending: false,
562 })
563 .await
564 .unwrap();
565 assert_eq!(
566 *all.downcast::<Vec<VersionedRecord<i64>>>().unwrap(),
567 vec![
568 VersionedRecord {
569 value: 10,
570 valid_from: 100,
571 valid_to: Some(200)
572 },
573 VersionedRecord {
574 value: 20,
575 valid_from: 200,
576 valid_to: Some(300)
577 },
578 VersionedRecord {
579 value: 30,
580 valid_from: 300,
581 valid_to: None
582 },
583 ]
584 );
585
586 let win = q
589 .iq2_execute(&Iq2Query::MultiVersionedKey {
590 key: Box::new("k".to_string()),
591 from_ts: Some(150),
592 to_ts: Some(250),
593 descending: true,
594 })
595 .await
596 .unwrap();
597 assert_eq!(
598 *win.downcast::<Vec<VersionedRecord<i64>>>().unwrap(),
599 vec![
600 VersionedRecord {
601 value: 20,
602 valid_from: 200,
603 valid_to: Some(300)
604 },
605 VersionedRecord {
606 value: 10,
607 valid_from: 100,
608 valid_to: Some(200)
609 },
610 ]
611 );
612
613 let bad = q
615 .iq2_execute(&Iq2Query::VersionedKey {
616 key: Box::new(7_i64),
617 as_of: None,
618 })
619 .await;
620 assert_eq!(bad.err(), Some(Iq2Failure::KeyTypeMismatch));
621 }
622
623 #[tokio::test]
624 async fn changelog_roundtrip_restores_chain() {
625 let mut s = store(secs(1_000));
626 s.put("k".into(), Some(10), 100).await;
627 s.delete(&"k".into(), 150).await;
628 s.put("k".into(), Some(30), 300).await;
629 let cl = s.take_changelog_ts();
630 let mut r = store(secs(1_000));
631 r.set_logging(false);
632 for (k, v, ts) in cl {
633 r.apply_changelog_ts(k, v, ts.unwrap()).await;
634 }
635 assert!(r.take_changelog().is_empty());
636 assert_eq!(r.get(&"k".into()).await.map(|x| x.value), Some(30));
637 assert_eq!(
638 r.get_as_of(&"k".into(), 120).await.map(|x| x.value),
639 Some(10)
640 );
641 assert_eq!(r.get_as_of(&"k".into(), 160).await, None);
642 }
643}