krabka_client_streams/store/api.rs
1//! Store traits.
2//!
3//! `StateStore` is object-safe, so the registry holds it erased. It carries the
4//! changelog hooks. Every #3 store is changelog-logged, so the erased registry
5//! can restore and drain through `&mut dyn StateStore`. `KeyValueStore<K,V>` is
6//! the typed get/put/delete surface.
7
8use std::any::Any;
9
10use async_trait::async_trait;
11
12/// Lifecycle, identity, and changelog hooks for any store.
13#[async_trait]
14pub trait StateStore: Any + Send {
15 fn name(&self) -> &str;
16 /// Flush pending state. This is a no-op for in-memory, where the changelog
17 /// gives durability.
18 async fn flush(&mut self);
19 fn close(&mut self);
20 /// Typed downcast hook that `get_state_store` uses.
21 fn as_any_mut(&mut self) -> &mut dyn Any;
22 /// The store's changelog topic (`<app>-<store>-changelog`).
23 fn changelog_topic(&self) -> &str;
24 /// Drain the buffered changelog entries: key bytes, and value bytes or None
25 /// for a tombstone.
26 fn take_changelog(&mut self) -> Vec<(bytes::Bytes, Option<bytes::Bytes>)>;
27 /// Like `take_changelog`, but each entry also carries an optional explicit
28 /// changelog RECORD timestamp. `None` means use the producer default, which
29 /// is send-time. Versioned stores override this method to emit the version
30 /// timestamp (KIP-889). The default wraps `take_changelog` with `None`.
31 fn take_changelog_ts(&mut self) -> Vec<(bytes::Bytes, Option<bytes::Bytes>, Option<i64>)> {
32 self.take_changelog()
33 .into_iter()
34 .map(|(k, v)| (k, v, None))
35 .collect()
36 }
37 /// Apply a changelog record during restore. It updates state and does NOT
38 /// re-log.
39 async fn apply_changelog(&mut self, key: bytes::Bytes, value: Option<bytes::Bytes>);
40 /// Like `apply_changelog`, but it carries the changelog record's timestamp.
41 /// The default ignores the timestamp and delegates. Versioned stores override
42 /// this method to insert the version at this timestamp.
43 async fn apply_changelog_ts(
44 &mut self,
45 key: bytes::Bytes,
46 value: Option<bytes::Bytes>,
47 _timestamp: i64,
48 ) {
49 self.apply_changelog(key, value).await;
50 }
51 /// Toggle changelog logging. It is off during restore and on during
52 /// processing.
53 fn set_logging(&mut self, on: bool);
54 /// IQ read view, if this store is interactively queryable. Default `None`.
55 fn as_iq(&self) -> Option<&dyn crate::store::iq::IqQueryable> {
56 None
57 }
58 /// Stash the current record's context, so a caching store can attach it to
59 /// the next write. The flush forwards that context with the deduped `Change`.
60 /// The default is a no-op.
61 fn set_record_context(&mut self, _ctx: crate::processor::record::RecordContext) {}
62 /// Erased record-cache hook.
63 ///
64 /// This method wraps the store's backend in the supplied internal
65 /// `NamedCache`, which is registered in the task's `ThreadCache`. It returns
66 /// `true` when this store kind is cache-aware. It lets `instantiate` enable
67 /// caching on a materialized KV store and never learn the store's `K` or `V`.
68 /// The default is `false`, which means not cacheable. Window and session
69 /// stores keep the default until their caching lands. KV stores override this
70 /// method and delegate to their typed `enable_cache`.
71 ///
72 /// `NamedCache` is crate-internal store plumbing. This method is reachable on
73 /// the `pub` trait, but no caller outside the crate can use it.
74 #[allow(private_interfaces)]
75 fn enable_cache_erased(
76 &mut self,
77 _cache: std::sync::Arc<std::sync::Mutex<crate::store::cache::named::NamedCache>>,
78 ) -> bool {
79 false
80 }
81 /// Erased query for whether this store's record cache is enabled.
82 ///
83 /// The cache is enabled when
84 /// [`enable_cache_erased`](Self::enable_cache_erased) has wrapped the
85 /// backend. A materializing processor uses this method to decide whether to
86 /// suppress its immediate downstream forward, and it never learns `K` or `V`.
87 /// The cache flush forwards the deduped `Change`. The default is `false`,
88 /// which means not cached and not cache-aware. KV stores override this
89 /// method.
90 fn is_cached_erased(&self) -> bool {
91 false
92 }
93 /// Flush this store's record cache, if it has one.
94 ///
95 /// This method writes the dirty entries through to the underlying store,
96 /// buffers their changelog records, and pushes the deduped downstream
97 /// `Record<K, Change<V>>` into `buffer`. It pushes one boxed copy PER child
98 /// in `children`, which matches the per-child clone in
99 /// `ProcessorContext::forward`. The default has no cache, so it is a no-op.
100 ///
101 /// `ErasedRecord` is crate-internal graph plumbing. This method is reachable
102 /// on the `pub` trait, but no caller outside the crate can use it.
103 #[allow(private_interfaces)]
104 async fn flush_cache_into(
105 &mut self,
106 _buffer: &mut std::collections::VecDeque<(usize, crate::processor::erased::ErasedRecord)>,
107 _children: &[usize],
108 ) {
109 }
110 /// Wipe every entry and any buffered changelog. The EOS rollback path uses
111 /// this method to reset the store to a clean slate before it re-restores from
112 /// the committed changelog.
113 async fn clear(&mut self);
114 /// Serialize the whole store into one barrier-snapshot payload.
115 ///
116 /// The task calls this method for every store it owns when a barrier fires,
117 /// and it puts the payloads into the frozen snapshot container under the
118 /// store names. See [`crate::store::snapshot`].
119 ///
120 /// The payload holds every piece of state that
121 /// [`restore_snapshot`](Self::restore_snapshot) needs to rebuild the store,
122 /// which for some stores is more than the key-value entries. A suppress
123 /// buffer also carries its sequence counter and its byte-size total.
124 ///
125 /// The payload format is Rust's own. The container around it is shared with
126 /// `krabka-streams-java` and `krabka-streams-go`, the payload inside it is
127 /// not.
128 async fn snapshot(&mut self) -> bytes::Bytes;
129 /// Replace the whole store with a payload that
130 /// [`snapshot`](Self::snapshot) produced.
131 ///
132 /// The store is wiped first, so the restored state is the snapshot and
133 /// nothing else. This method does NOT re-log, exactly as `apply_changelog`
134 /// does not.
135 ///
136 /// # Errors
137 ///
138 /// Returns [`StreamsClientError::Snapshot`](crate::StreamsClientError::Snapshot)
139 /// when the payload does not match the format this store writes.
140 async fn restore_snapshot(
141 &mut self,
142 data: bytes::Bytes,
143 ) -> Result<(), crate::error::StreamsClientError>;
144}
145
146/// A keyed store. The in-memory store implements it, and a processor gets this
147/// typed view from `ProcessorContext::get_state_store`.
148///
149/// `K` needs `Send + Sync` because `get` and `delete` take `&K`, and the boxed
150/// store future must be `Send`. The whole execution chain runs inside
151/// `tokio::spawn`: `Graph::pipe`, then the processors, then the store ops. A
152/// `&K` is only `Send` when `K: Sync`.
153#[async_trait]
154pub trait KeyValueStore<K: Send + Sync, V: Send>: StateStore {
155 async fn get(&self, key: &K) -> Option<V>;
156 async fn put(&mut self, key: K, value: V);
157 async fn delete(&mut self, key: &K) -> Option<V>;
158 /// Half-open range scan `[lo, hi)` in memcmp (lexicographic) key order.
159 async fn range(&self, lo: &K, hi: &K) -> Vec<(K, V)>;
160}