1use std::{
7 any::Any,
8 sync::{Arc, Mutex},
9};
10
11use async_trait::async_trait;
12use bytes::Bytes;
13
14use crate::{
15 processor::{record::RecordContext, serde::Serde},
16 store::{
17 api::StateStore,
18 byte::{ByteKeyValueStore, InMemoryBytes},
19 cache::{named::NamedCache, session::CachingSessionStore},
20 session_schema::{session_end_of, session_key, session_key_bytes_of, session_start_of},
21 },
22};
23
24enum Backing {
30 Plain(Box<dyn ByteKeyValueStore>),
31 Cached(CachingSessionStore),
32}
33
34impl Backing {
35 async fn range(&self, lo: &[u8], hi: &[u8]) -> Vec<(Bytes, Bytes)> {
36 match self {
37 Backing::Plain(b) => b.range(lo, hi).await,
38 Backing::Cached(c) => c.range(lo, hi).await,
39 }
40 }
41 async fn scan_all(&self) -> Vec<(Bytes, Bytes)> {
42 match self {
43 Backing::Plain(b) => b.scan_all().await,
44 Backing::Cached(c) => c.scan_all().await,
45 }
46 }
47
48 async fn put(&mut self, key: Bytes, value: Bytes, ctx: RecordContext) {
53 match self {
54 Backing::Plain(b) => b.put(key, value).await,
55 Backing::Cached(c) => c.put(key, value, ctx).await,
56 }
57 }
58
59 async fn remove(&mut self, key: Bytes, ctx: RecordContext) {
64 match self {
65 Backing::Plain(b) => {
66 b.delete(&key).await;
67 }
68 Backing::Cached(c) => c.remove(key, ctx).await,
69 }
70 }
71
72 async fn apply(&mut self, key: Bytes, value: Option<Bytes>) {
76 match (self, value) {
77 (Backing::Plain(b), Some(v)) => b.put(key, v).await,
78 (Backing::Plain(b), None) => {
79 b.delete(&key).await;
80 }
81 (Backing::Cached(c), Some(v)) => c.put_inner(key, v).await,
82 (Backing::Cached(c), None) => c.delete_inner(&key).await,
83 }
84 }
85
86 async fn clear(&mut self) {
87 match self {
88 Backing::Plain(b) => b.clear().await,
89 Backing::Cached(c) => c.clear().await,
90 }
91 }
92
93 async fn snapshot(&self) -> Bytes {
95 crate::store::snapshot::encode_byte_entries(&self.scan_all().await)
96 }
97
98 async fn restore(&mut self, data: &[u8]) -> Result<(), crate::error::StreamsClientError> {
101 let entries = crate::store::snapshot::decode_byte_entries(data)?;
102 self.clear().await;
103 for (key, value) in entries {
104 self.apply(key, Some(value)).await;
105 }
106 Ok(())
107 }
108}
109
110#[async_trait]
116pub trait SessionStore<K: Send + Sync, V: Send>: StateStore {
117 async fn find_sessions(
123 &self,
124 key: &K,
125 earliest_end: i64,
126 latest_start: i64,
127 ) -> Vec<(i64, i64, V)>;
128 async fn put(&mut self, key: K, start: i64, end: i64, value: V);
129 async fn remove(&mut self, key: &K, start: i64, end: i64);
130 async fn find_closed_sessions(&self, close_time: i64) -> Vec<(K, i64, i64, V)>;
135}
136
137pub struct SessionBytesStore<K, V> {
138 name: String,
139 changelog_topic: String,
140 backing: Backing,
141 key_serde: Box<dyn Serde<K>>,
142 value_serde: Box<dyn Serde<V>>,
143 changelog: Vec<(Bytes, Option<Bytes>)>,
144 logging: bool,
145 pending_ctx: Option<RecordContext>,
149}
150
151impl<K: 'static, V: 'static> SessionBytesStore<K, V> {
152 #[must_use]
153 pub(crate) fn new(
154 name: String,
155 backend: Box<dyn ByteKeyValueStore>,
156 key_serde: Box<dyn Serde<K>>,
157 value_serde: Box<dyn Serde<V>>,
158 changelog_topic: String,
159 ) -> Self {
160 Self {
161 name,
162 changelog_topic,
163 backing: Backing::Plain(backend),
164 key_serde,
165 value_serde,
166 changelog: Vec::new(),
167 logging: true,
168 pending_ctx: None,
169 }
170 }
171
172 #[must_use]
173 pub fn in_memory(
174 name: String,
175 key_serde: Box<dyn Serde<K>>,
176 value_serde: Box<dyn Serde<V>>,
177 changelog_topic: String,
178 ) -> Self {
179 Self::new(
180 name,
181 Box::new(InMemoryBytes::default()),
182 key_serde,
183 value_serde,
184 changelog_topic,
185 )
186 }
187
188 pub(crate) fn enable_cache(&mut self, cache: Arc<Mutex<NamedCache>>) {
194 if !matches!(self.backing, Backing::Plain(_)) {
195 return; }
197 let placeholder = Backing::Plain(Box::new(InMemoryBytes::default()));
198 let Backing::Plain(backend) = std::mem::replace(&mut self.backing, placeholder) else {
199 unreachable!("guarded by the matches! above")
200 };
201 self.backing = Backing::Cached(CachingSessionStore::with_name(
202 cache,
203 backend,
204 self.name.clone(),
205 ));
206 }
207
208 #[must_use]
210 pub(crate) fn is_cached(&self) -> bool {
211 matches!(self.backing, Backing::Cached(_))
212 }
213
214 fn write_ctx(&self) -> RecordContext {
220 self.pending_ctx.clone().unwrap_or(RecordContext {
221 topic: self.changelog_topic.clone(),
222 partition: 0,
223 offset: 0,
224 timestamp: 0,
225 })
226 }
227}
228
229#[async_trait]
230impl<K: Send + 'static, V: Send + 'static> StateStore for SessionBytesStore<K, V> {
231 fn name(&self) -> &str {
232 &self.name
233 }
234 async fn flush(&mut self) {}
235 fn close(&mut self) {}
236 fn as_any_mut(&mut self) -> &mut dyn Any {
237 self
238 }
239 fn changelog_topic(&self) -> &str {
240 &self.changelog_topic
241 }
242 fn take_changelog(&mut self) -> Vec<(Bytes, Option<Bytes>)> {
243 std::mem::take(&mut self.changelog)
244 }
245 async fn apply_changelog(&mut self, key: Bytes, value: Option<Bytes>) {
246 self.backing.apply(key, value).await;
249 }
250 fn set_logging(&mut self, on: bool) {
251 self.logging = on;
252 }
253 fn as_iq(&self) -> Option<&dyn crate::store::iq::IqQueryable> {
254 Some(self)
255 }
256 fn set_record_context(&mut self, ctx: RecordContext) {
257 self.pending_ctx = Some(ctx);
258 }
259 #[allow(private_interfaces)]
260 fn enable_cache_erased(&mut self, cache: Arc<Mutex<NamedCache>>) -> bool {
261 self.enable_cache(cache);
262 true
263 }
264 fn is_cached_erased(&self) -> bool {
265 self.is_cached()
266 }
267 #[allow(private_interfaces)]
268 async fn flush_cache_into(
269 &mut self,
270 buffer: &mut std::collections::VecDeque<(usize, crate::processor::erased::ErasedRecord)>,
271 children: &[usize],
272 ) {
273 use crate::{
274 dsl::{
275 processors::change::Change,
276 windows::{Window, Windowed},
277 },
278 processor::erased::ErasedRecord,
279 };
280 let Backing::Cached(cache) = &self.backing else {
281 return;
282 };
283 let drained = cache.flush_with_old().await;
286 for (sk, old_vb, new_vb, ctx) in drained {
287 if self.logging {
290 self.changelog.push((sk.clone(), new_vb.clone()));
291 }
292 let session_start = session_start_of(&sk);
294 let session_end = session_end_of(&sk);
295 let key_bytes = session_key_bytes_of(&sk);
296 for &child in children {
297 let key: K = self
298 .key_serde
299 .deserialize(&self.changelog_topic, key_bytes)
300 .expect("flush_cache_into session key deserialize");
301 let old: Option<V> = old_vb.as_ref().map(|b| {
302 self.value_serde
303 .deserialize(&self.changelog_topic, b)
304 .expect("flush_cache_into session old value deserialize")
305 });
306 let new: Option<V> = new_vb.as_ref().map(|b| {
307 self.value_serde
308 .deserialize(&self.changelog_topic, b)
309 .expect("flush_cache_into session new value deserialize")
310 });
311 let windowed = Windowed {
312 key,
313 window: Window {
314 start: session_start,
315 end: session_end,
316 },
317 };
318 let change = Change { old, new };
319 buffer.push_back((
320 child,
321 ErasedRecord::new(Some(Box::new(windowed)), Box::new(change), ctx.timestamp),
322 ));
323 }
324 }
325 }
326 async fn clear(&mut self) {
327 self.backing.clear().await;
328 self.changelog.clear();
329 }
330 async fn snapshot(&mut self) -> Bytes {
331 self.backing.snapshot().await
332 }
333 async fn restore_snapshot(
334 &mut self,
335 data: Bytes,
336 ) -> Result<(), crate::error::StreamsClientError> {
337 self.backing.restore(&data).await?;
338 self.changelog.clear();
340 Ok(())
341 }
342}
343
344#[async_trait::async_trait]
347impl<K: 'static, V: 'static> crate::store::iq::IqQueryable for SessionBytesStore<K, V> {
348 fn kind(&self) -> crate::store::iq::StoreKind {
349 crate::store::iq::StoreKind::Session
350 }
351 async fn iq_session_fetch_key(&self, key: &[u8]) -> Vec<((i64, i64), bytes::Bytes)> {
352 let lo = session_key(key, 0, 0);
353 let hi = session_key(key, i64::MAX, i64::MAX);
354 let mut out = Vec::new();
355 for (k, raw) in self.backing.range(&lo, &hi).await {
356 if session_key_bytes_of(&k) != key {
357 continue;
358 }
359 let start = session_start_of(&k);
360 let end = session_end_of(&k);
361 out.push(((start, end), bytes::Bytes::copy_from_slice(&raw)));
362 }
363 out
364 }
365}
366
367#[async_trait]
368impl<K: Send + Sync + 'static, V: Send + 'static> SessionStore<K, V> for SessionBytesStore<K, V> {
369 async fn find_sessions(
370 &self,
371 key: &K,
372 earliest_end: i64,
373 latest_start: i64,
374 ) -> Vec<(i64, i64, V)> {
375 let kb = self.key_serde.serialize(&self.changelog_topic, key);
376 let lo = session_key(&kb, 0, earliest_end.max(0));
379 let hi = session_key(&kb, i64::MAX, i64::MAX);
381 let mut out = Vec::new();
382 for (k, raw) in self.backing.range(&lo, &hi).await {
383 if session_key_bytes_of(&k) != kb.as_ref() {
384 continue; }
386 let end = session_end_of(&k);
387 let start = session_start_of(&k);
388 if end >= earliest_end && start <= latest_start {
389 out.push((
390 start,
391 end,
392 self.value_serde
393 .deserialize(&self.changelog_topic, &raw)
394 .expect("session value deserialize"),
395 ));
396 }
397 }
398 out
399 }
400
401 async fn put(&mut self, key: K, start: i64, end: i64, value: V) {
402 let kb = self.key_serde.serialize(&self.changelog_topic, &key);
403 let sk = session_key(&kb, start, end);
404 let raw = self.value_serde.serialize(&self.changelog_topic, &value);
405 match &self.backing {
406 Backing::Plain(_) => {
408 self.backing
409 .put(sk.clone(), raw.clone(), self.write_ctx())
410 .await;
411 if self.logging {
412 self.changelog.push((sk, Some(raw)));
413 }
414 }
415 Backing::Cached(_) => {
418 let ctx = self.write_ctx();
419 self.backing.put(sk, raw, ctx).await;
420 }
421 }
422 }
423
424 async fn remove(&mut self, key: &K, start: i64, end: i64) {
425 let kb = self.key_serde.serialize(&self.changelog_topic, key);
426 let sk = session_key(&kb, start, end);
427 let cached = matches!(self.backing, Backing::Cached(_));
428 let ctx = self.write_ctx();
429 self.backing.remove(sk.clone(), ctx).await;
430 if self.logging && !cached {
432 self.changelog.push((sk, None));
433 }
434 }
435
436 async fn find_closed_sessions(&self, close_time: i64) -> Vec<(K, i64, i64, V)> {
437 let mut out = Vec::new();
438 for (k, raw) in self.backing.scan_all().await {
439 let end = session_end_of(&k);
440 if end > close_time {
441 continue;
442 }
443 let key = self
444 .key_serde
445 .deserialize(&self.changelog_topic, session_key_bytes_of(&k))
446 .expect("session key deserialize");
447 let value = self
448 .value_serde
449 .deserialize(&self.changelog_topic, &raw)
450 .expect("session value deserialize");
451 out.push((key, session_start_of(&k), end, value));
452 }
453 out
454 }
455}
456
457#[cfg(test)]
458mod tests {
459 use super::*;
460 use crate::processor::serde::{I64Serde, StringSerde};
461
462 fn store() -> SessionBytesStore<String, i64> {
463 SessionBytesStore::<String, i64>::in_memory(
464 "s".into(),
465 Box::new(StringSerde),
466 Box::new(I64Serde),
467 "app-s-changelog".into(),
468 )
469 }
470
471 #[tokio::test]
472 async fn put_find_remove_and_changelog() {
473 let mut s = store();
474 s.put("k".to_string(), 0, 10, 1).await; s.put("k".to_string(), 50, 60, 2).await; let found = s.find_sessions(&"k".to_string(), -5, 35).await;
479 assert_eq!(found, vec![(0, 10, 1)]);
480 s.remove(&"k".to_string(), 0, 10).await;
482 assert_eq!(s.find_sessions(&"k".to_string(), -5, 35).await, vec![]);
483 let cl = s.take_changelog();
485 assert_eq!(cl.len(), 3);
486 assert!(cl[2].1.is_none());
487 }
488
489 #[tokio::test]
490 async fn find_sessions_returns_store_order_end_then_start() {
491 let mut s = store();
492 s.put("k".to_string(), 0, 30, 1).await;
493 s.put("k".to_string(), 0, 10, 2).await;
494 let found = s.find_sessions(&"k".to_string(), 0, 100).await;
496 assert_eq!(found, vec![(0, 10, 2), (0, 30, 1)]);
497 }
498
499 #[tokio::test]
500 async fn other_key_prefix_is_not_returned() {
501 let mut s = store();
502 s.put("k".to_string(), 0, 10, 1).await;
503 s.put("kk".to_string(), 0, 10, 9).await; let found = s.find_sessions(&"k".to_string(), 0, 100).await;
505 assert_eq!(found, vec![(0, 10, 1)]);
506 }
507
508 #[tokio::test]
509 async fn find_closed_sessions_scans_by_end() {
510 let mut s = SessionBytesStore::<String, i64>::in_memory(
511 "s".into(),
512 Box::new(StringSerde),
513 Box::new(I64Serde),
514 "app-s-changelog".into(),
515 );
516 s.put("a".into(), 0, 5, 1).await;
518 s.put("b".into(), 2, 8, 2).await;
519 s.put("a".into(), 20, 30, 3).await;
520
521 let mut got = s.find_closed_sessions(8).await;
523 got.sort();
524 assert_eq!(
525 got,
526 vec![("a".to_string(), 0, 5, 1), ("b".to_string(), 2, 8, 2)]
527 );
528 assert!(s.find_closed_sessions(4).await.is_empty());
530 }
531
532 #[tokio::test]
533 async fn restore_via_changelog_rebuilds_sessions() {
534 let mut s = store();
535 s.put("k".to_string(), 0, 10, 1).await;
536 s.put("k".to_string(), 50, 60, 2).await;
537 s.remove(&"k".to_string(), 0, 10).await; let cl = s.take_changelog();
539 let mut s2 = store();
541 for (k, v) in cl {
542 s2.apply_changelog(k, v).await;
543 }
544 assert_eq!(
546 s2.find_sessions(&"k".to_string(), 0, 100).await,
547 vec![(50, 60, 2)]
548 );
549 }
550
551 fn cached_store() -> SessionBytesStore<String, i64> {
554 let mut s = store();
555 s.enable_cache(Arc::new(Mutex::new(NamedCache::new("s".into()))));
556 s
557 }
558
559 fn ctx_at(ts: i64) -> RecordContext {
560 RecordContext {
561 topic: "t".into(),
562 partition: 0,
563 offset: 0,
564 timestamp: ts,
565 }
566 }
567
568 #[tokio::test]
569 async fn cached_session_store_reads_your_writes() {
570 let mut s = cached_store();
571 s.set_record_context(ctx_at(0));
572 s.put("a".into(), 0, 10, 1).await;
574 s.put("a".into(), 0, 10, 2).await;
575 assert_eq!(
577 s.find_sessions(&"a".to_string(), 0, 100).await,
578 vec![(0, 10, 2)]
579 );
580 assert!(s.take_changelog().is_empty());
582 }
583
584 #[tokio::test]
585 async fn flush_cache_into_emits_deduped_session_change() {
586 use crate::dsl::{
587 processors::change::Change,
588 windows::{Window, Windowed},
589 };
590 let mut s = cached_store();
591 s.set_record_context(ctx_at(0));
593 s.put("a".into(), 0, 10, 1).await;
594 let mut seed = std::collections::VecDeque::new();
595 s.flush_cache_into(&mut seed, &[0]).await;
596 let _ = s.take_changelog();
597
598 s.set_record_context(ctx_at(7));
600 s.put("a".into(), 0, 10, 2).await;
601 s.put("a".into(), 0, 10, 3).await;
602
603 let mut buffer = std::collections::VecDeque::new();
604 s.flush_cache_into(&mut buffer, &[7]).await;
605 assert_eq!(buffer.len(), 1);
606 let (child, rec) = &buffer[0];
607 assert_eq!(*child, 7);
608 assert_eq!(rec.timestamp, 7);
609 let key = rec
611 .key
612 .as_ref()
613 .unwrap()
614 .downcast_ref::<Windowed<String>>()
615 .unwrap();
616 assert_eq!(key.key, "a");
617 assert_eq!(key.window, Window { start: 0, end: 10 });
618 let change = rec.value.downcast_ref::<Change<i64>>().unwrap();
620 assert_eq!(change.old, Some(1));
621 assert_eq!(change.new, Some(3));
622
623 let cl = s.take_changelog();
625 assert_eq!(cl.len(), 1);
626 assert_eq!(
627 cl[0].0,
628 session_key(
629 &StringSerde.serialize("app-s-changelog", &"a".to_string()),
630 0,
631 10
632 )
633 );
634 assert_eq!(cl[0].1, Some(I64Serde.serialize("app-s-changelog", &3)));
635
636 assert_eq!(
638 s.find_sessions(&"a".to_string(), 0, 100).await,
639 vec![(0, 10, 3)]
640 );
641 }
642
643 #[tokio::test]
647 async fn cached_find_closed_sessions_overlays_cache() {
648 let mut s = cached_store();
649 s.set_record_context(ctx_at(0));
650 s.put("a".into(), 0, 5, 1).await;
651 s.put("b".into(), 2, 8, 2).await;
652 s.put("a".into(), 20, 30, 3).await;
653
654 let mut got = s.find_closed_sessions(8).await;
655 got.sort();
656 assert_eq!(
657 got,
658 vec![("a".to_string(), 0, 5, 1), ("b".to_string(), 2, 8, 2)]
659 );
660 assert!(s.find_closed_sessions(4).await.is_empty());
661 assert!(s.take_changelog().is_empty());
663 }
664
665 #[tokio::test]
669 async fn cached_remove_stages_tombstone_and_flushes() {
670 use crate::dsl::processors::change::Change;
671 let mut s = cached_store();
672 s.set_record_context(ctx_at(0));
674 s.put("a".into(), 0, 10, 1).await;
675 let mut seed = std::collections::VecDeque::new();
676 s.flush_cache_into(&mut seed, &[0]).await;
677 let _ = s.take_changelog();
678
679 s.set_record_context(ctx_at(7));
681 s.remove(&"a".to_string(), 0, 10).await;
682 assert!(s.take_changelog().is_empty());
683 assert!(s.find_sessions(&"a".to_string(), 0, 100).await.is_empty());
684
685 let mut buffer = std::collections::VecDeque::new();
686 s.flush_cache_into(&mut buffer, &[0]).await;
687 assert_eq!(buffer.len(), 1);
688 let change = buffer[0].1.value.downcast_ref::<Change<i64>>().unwrap();
689 assert_eq!(change.old, Some(1)); assert_eq!(change.new, None); let cl = s.take_changelog();
692 assert_eq!(cl.len(), 1);
693 assert!(cl[0].1.is_none()); }
695
696 #[tokio::test]
699 async fn cached_apply_changelog_goes_below_cache() {
700 let mut s = cached_store();
701 let sk = session_key(
702 &StringSerde.serialize("app-s-changelog", &"a".to_string()),
703 0,
704 10,
705 );
706 s.apply_changelog(sk.clone(), Some(I64Serde.serialize("app-s-changelog", &9)))
707 .await;
708 assert_eq!(
709 s.find_sessions(&"a".to_string(), 0, 100).await,
710 vec![(0, 10, 9)]
711 );
712 let mut buffer = std::collections::VecDeque::new();
713 s.flush_cache_into(&mut buffer, &[0]).await;
714 assert!(buffer.is_empty());
715 assert!(s.take_changelog().is_empty());
716
717 s.apply_changelog(sk, None).await;
719 assert!(s.find_sessions(&"a".to_string(), 0, 100).await.is_empty());
720 }
721
722 #[tokio::test]
724 async fn cached_clear_empties_everything() {
725 let mut s = cached_store();
726 s.set_record_context(ctx_at(0));
727 s.put("a".into(), 0, 10, 1).await;
728 StateStore::clear(&mut s).await;
729 assert!(s.find_sessions(&"a".to_string(), 0, 100).await.is_empty());
730 assert!(s.find_closed_sessions(i64::MAX).await.is_empty());
731 let mut buffer = std::collections::VecDeque::new();
732 s.flush_cache_into(&mut buffer, &[0]).await;
733 assert!(buffer.is_empty());
734 }
735
736 #[tokio::test]
739 async fn enable_cache_is_idempotent() {
740 let mut s = store();
741 assert!(!s.is_cached());
742 s.enable_cache(Arc::new(Mutex::new(NamedCache::new("s".into()))));
743 assert!(s.is_cached());
744 s.enable_cache(Arc::new(Mutex::new(NamedCache::new("s".into()))));
745 assert!(s.is_cached());
746 }
747
748 #[tokio::test]
752 async fn plain_store_lifecycle_logging_flush_close_clear() {
753 let mut s = store();
754 s.set_logging(false);
755 s.put("a".into(), 0, 10, 1).await;
756 assert!(
757 s.take_changelog().is_empty(),
758 "logging off suppresses the changelog"
759 );
760 assert_eq!(
762 s.find_sessions(&"a".to_string(), 0, 100).await,
763 vec![(0, 10, 1)]
764 );
765 s.flush().await;
766 s.close();
767
768 s.set_logging(true);
770 s.put("b".into(), 0, 10, 2).await;
771 StateStore::clear(&mut s).await;
772 assert!(s.find_sessions(&"b".to_string(), 0, 100).await.is_empty());
773 assert!(s.take_changelog().is_empty());
774 }
775
776 #[tokio::test]
777 async fn plain_session_store_unchanged() {
778 let mut s = store();
779 s.put("a".into(), 0, 10, 1).await;
780 assert_eq!(
781 s.find_sessions(&"a".to_string(), 0, 100).await,
782 vec![(0, 10, 1)]
783 );
784 let cl = s.take_changelog();
785 assert_eq!(cl.len(), 1);
786 let mut buffer = std::collections::VecDeque::new();
787 s.flush_cache_into(&mut buffer, &[0]).await;
788 assert!(buffer.is_empty());
789 }
790}