1pub mod config;
9pub mod rolling;
10pub mod rolling_incremental;
11pub mod rolling_top_k;
12pub mod tumbling;
13pub mod tumbling_carry;
14
15use std::collections::HashMap;
16
17use reifydb_codec::{
18 key::{
19 encode_u64_asc,
20 encoded::{EncodedKey, EncodedKeyRange, IntoEncodedKey},
21 },
22 row::operator::{OperatorState, decode},
23};
24use reifydb_core::{
25 key::operator_state::{
26 GroupId, GroupStateKey, IntoGroupStateKey, Keyspace, OperatorStateKey, keyspace_inner_range,
27 },
28 metrics::heap::HeapSize,
29 state::{cache::StateCache, store::StateStore},
30};
31use reifydb_macro::operator_state;
32use reifydb_value::{Result, value::row_number::RowNumber};
33use tracing::{debug, instrument};
34
35use crate::{
36 operator::state::seal::coord::Coord,
37 window::span::{Slot, WindowSpan},
38};
39
40pub enum AccumulatorEvent<C> {
42 Add(C),
43 Remove(C),
44}
45
46fn note_when_expiry_capped(expired: usize, expire_batch: usize) {
47 if expired >= expire_batch {
48 debug!(expired, expire_batch, "window expiry hit per-tick batch cap, backlog deferred to next tick");
49 }
50}
51
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54pub enum EmitKind {
55 Insert,
56 Update,
57 Remove,
58}
59
60pub struct WindowResult<G, Coord, Output> {
62 pub row_number: RowNumber,
63 pub group: G,
64 pub span: WindowSpan<Coord>,
65 pub value: Output,
66 pub prior: Option<Output>,
69 pub kind: EmitKind,
70}
71
72#[operator_state]
74#[derive(Debug, Clone)]
75pub struct GroupMeta<K> {
76 pub high_water: Option<K>,
77}
78
79impl<K> Default for GroupMeta<K> {
80 fn default() -> Self {
81 Self {
82 high_water: None,
83 }
84 }
85}
86
87impl<K> HeapSize for GroupMeta<K> {
88 fn heap_size(&self) -> usize {
89 0
90 }
91}
92
93pub(crate) trait MetaHighWater: OperatorState {
97 fn high_water_order(&self) -> Option<u64>;
98}
99
100impl<C: Slot> MetaHighWater for GroupMeta<C> {
101 fn high_water_order(&self) -> Option<u64> {
102 self.high_water.map(|hw| hw.order_key().to_order())
103 }
104}
105
106pub(crate) struct BatchMeta<C> {
109 pub(crate) initial: Option<C>,
110 pub(crate) bumped: Option<C>,
111}
112
113impl<C> Default for BatchMeta<C> {
114 fn default() -> Self {
115 Self {
116 initial: None,
117 bumped: None,
118 }
119 }
120}
121
122impl<C: Slot> BatchMeta<C> {
123 pub(crate) fn observe(&mut self, coord: C) {
124 match self.high_water() {
125 Some(hw) if coord > hw => self.bumped = Some(coord),
126 None => self.bumped = Some(coord),
127 _ => {}
128 }
129 }
130
131 pub(crate) fn high_water(&self) -> Option<C> {
132 self.bumped.or(self.initial)
133 }
134}
135
136pub(crate) fn load_batch_meta<C>(
137 store: &mut dyn StateStore,
138 meta: &mut StateCache<MetaKey, GroupMeta<C>>,
139 key: &MetaKey,
140) -> Result<BatchMeta<C>>
141where
142 C: Slot,
143{
144 let initial = meta.get(store, key)?.and_then(|meta| meta.high_water);
145 Ok(BatchMeta {
146 initial,
147 bumped: None,
148 })
149}
150
151pub(crate) fn persist_batch_meta<G, C>(
152 store: &mut dyn StateStore,
153 meta: &mut StateCache<MetaKey, GroupMeta<C>>,
154 loaded: HashMap<G, BatchMeta<C>>,
155) -> Result<()>
156where
157 for<'a> &'a G: IntoEncodedKey,
158 C: Slot,
159{
160 for (group, batch) in loaded {
161 let Some(bumped) = batch.bumped else {
162 continue;
163 };
164 meta.modify(store, &meta_key_for(&group), |native| {
165 if native.high_water.is_none_or(|hw| bumped > hw) {
166 native.high_water = Some(bumped);
167 }
168 })?;
169 }
170 Ok(())
171}
172
173pub(crate) fn meta_range() -> EncodedKeyRange {
176 keyspace_inner_range(GroupId::ROOT, Keyspace::WINDOW_META)
177}
178
179#[instrument(name = "flow::window::sweep_stale_meta", level = "debug", skip_all)]
185pub(crate) fn sweep_stale_meta<M>(
186 store: &mut dyn StateStore,
187 meta: &mut StateCache<MetaKey, M>,
188 threshold: u64,
189 low_water: &mut Option<u64>,
190) -> Result<usize>
191where
192 M: MetaHighWater + Clone + OperatorState + HeapSize,
193{
194 if low_water.is_some_and(|lw| lw >= threshold) {
195 return Ok(0);
196 }
197 let mut stale: Vec<MetaKey> = Vec::new();
198 let mut min_surviving: Option<u64> = None;
199 store.state_range_visit(meta_range(), None, &mut |key, bytes| {
200 if let Some(hw) = decode::<M>(&bytes)?.high_water_order() {
201 if hw < threshold {
202 let Some(key) = decode_meta_key(key.as_encoded()) else {
203 return Ok(());
204 };
205 stale.push(key);
206 } else {
207 min_surviving = Some(min_surviving.map_or(hw, |m| m.min(hw)));
208 }
209 }
210 Ok(())
211 })?;
212 *low_water = min_surviving;
213 let count = stale.len();
214 for key in &stale {
215 meta.remove(store, key)?;
216 }
217 Ok(count)
218}
219
220#[derive(Clone, Hash, PartialEq, Eq)]
223pub struct MetaKey(pub EncodedKey);
224
225impl HeapSize for MetaKey {
226 fn heap_size(&self) -> usize {
227 self.0.heap_size()
228 }
229}
230
231#[derive(Clone, Hash, PartialEq, Eq)]
232pub struct RunningKey {
233 pub group: GroupId,
234 pub slot: EncodedKey,
235}
236
237impl RunningKey {
238 pub fn new(group: GroupId, slot: EncodedKey) -> Self {
239 Self {
240 group,
241 slot,
242 }
243 }
244
245 pub fn of_row(group: GroupId, row: RowNumber) -> Self {
246 Self::new(group, EncodedKey::new(encode_u64_asc(row.0)))
247 }
248}
249
250impl HeapSize for RunningKey {
251 fn heap_size(&self) -> usize {
252 match &self.slot {
253 EncodedKey::Inline {
254 ..
255 } => 0,
256 EncodedKey::Shared(bytes) => bytes.len(),
257 }
258 }
259}
260
261impl IntoGroupStateKey for &RunningKey {
262 fn into_group_state_key(self) -> GroupStateKey {
263 OperatorStateKey::inner_encoded(self.group, Keyspace::RUNNING, self.slot.as_bytes())
264 }
265}
266
267#[derive(Clone, Hash, PartialEq, Eq)]
268pub struct WindowStateKey {
269 pub group: GroupId,
270 pub slot: EncodedKey,
271}
272
273impl WindowStateKey {
274 pub fn new(group: GroupId, slot: EncodedKey) -> Self {
275 Self {
276 group,
277 slot,
278 }
279 }
280
281 pub fn root(slot: EncodedKey) -> Self {
282 Self::new(GroupId::ROOT, slot)
283 }
284
285 pub fn of_row(group: GroupId, row: RowNumber) -> Self {
286 Self::new(group, EncodedKey::new(encode_u64_asc(row.0)))
287 }
288}
289
290impl HeapSize for WindowStateKey {
291 fn heap_size(&self) -> usize {
292 match &self.slot {
293 EncodedKey::Inline {
294 ..
295 } => 0,
296 EncodedKey::Shared(bytes) => bytes.len(),
297 }
298 }
299}
300
301impl IntoGroupStateKey for &WindowStateKey {
302 fn into_group_state_key(self) -> GroupStateKey {
303 OperatorStateKey::inner_encoded(self.group, Keyspace::ACCUMULATOR, self.slot.as_bytes())
304 }
305}
306
307#[derive(Clone, Hash, PartialEq, Eq)]
308pub struct BufferKey {
309 pub group: GroupId,
310 pub slot: EncodedKey,
311}
312
313impl BufferKey {
314 pub fn new(group: GroupId, slot: EncodedKey) -> Self {
315 Self {
316 group,
317 slot,
318 }
319 }
320
321 pub fn of_row(group: GroupId, row: RowNumber) -> Self {
322 Self::new(group, EncodedKey::new(encode_u64_asc(row.0)))
323 }
324}
325
326impl HeapSize for BufferKey {
327 fn heap_size(&self) -> usize {
328 match &self.slot {
329 EncodedKey::Inline {
330 ..
331 } => 0,
332 EncodedKey::Shared(bytes) => bytes.len(),
333 }
334 }
335}
336
337impl IntoGroupStateKey for &BufferKey {
338 fn into_group_state_key(self) -> GroupStateKey {
339 OperatorStateKey::inner_encoded(self.group, Keyspace::BUFFER, self.slot.as_bytes())
340 }
341}
342
343#[derive(Clone, Copy, Hash, PartialEq, Eq)]
344pub struct EmitKey {
345 pub group: GroupId,
346 pub row: RowNumber,
347}
348
349impl EmitKey {
350 pub fn new(group: GroupId, row: RowNumber) -> Self {
351 Self {
352 group,
353 row,
354 }
355 }
356}
357
358impl HeapSize for EmitKey {
359 fn heap_size(&self) -> usize {
360 0
361 }
362}
363
364impl IntoGroupStateKey for &EmitKey {
365 fn into_group_state_key(self) -> GroupStateKey {
366 OperatorStateKey::inner_encoded(self.group, Keyspace::EMIT, encode_u64_asc(self.row.0))
367 }
368}
369
370impl IntoGroupStateKey for &MetaKey {
371 fn into_group_state_key(self) -> GroupStateKey {
372 OperatorStateKey::inner_encoded(GroupId::ROOT, Keyspace::WINDOW_META, &self.0)
373 }
374}
375
376pub fn meta_key_for<G>(group: &G) -> MetaKey
377where
378 for<'a> &'a G: IntoEncodedKey,
379{
380 MetaKey(group.into_encoded_key())
381}
382
383#[derive(Debug, Clone, Copy, PartialEq, Eq)]
386pub enum ExpiryAnchor {
387 Unindexed,
390 WindowStart,
392 LastEvent,
394}
395
396impl ExpiryAnchor {
397 pub fn of(&self, window_start: u64, last_event: Option<u64>) -> Option<u64> {
401 match self {
402 ExpiryAnchor::Unindexed => None,
403 ExpiryAnchor::WindowStart => Some(window_start),
404 ExpiryAnchor::LastEvent => last_event,
405 }
406 }
407}
408
409pub(crate) fn decode_window_state_key(key: &EncodedKey) -> Option<WindowStateKey> {
410 let (group, keyspace, suffix) = OperatorStateKey::decode_inner(key.as_bytes())?;
411 if keyspace != Keyspace::ACCUMULATOR {
412 return None;
413 }
414 Some(WindowStateKey::new(group, EncodedKey::new(suffix)))
415}
416
417pub(crate) fn decode_meta_key(key: &EncodedKey) -> Option<MetaKey> {
418 let (group, keyspace, suffix) = OperatorStateKey::decode_inner(key.as_bytes())?;
419 (group == GroupId::ROOT && keyspace == Keyspace::WINDOW_META).then(|| MetaKey(EncodedKey::new(suffix)))
420}
421
422#[cfg(test)]
423mod archived_projection_tests {
424 use reifydb_value::value::datetime::DateTime;
425
426 use super::*;
427
428 fn via_storage<M: MetaHighWater>(meta: &M) -> Option<u64> {
430 let bytes = meta.encode_state(DateTime::EPOCH).unwrap();
431 decode::<M>(&bytes).unwrap().high_water_order()
432 }
433
434 #[test]
435 fn stored_high_water_yields_the_slot_order_key() {
436 let instant = DateTime::from_epoch_millis(1_700_000_000_123).unwrap();
438 let datetime_meta = GroupMeta {
439 high_water: Some(instant),
440 };
441 assert_eq!(
442 via_storage(&datetime_meta),
443 Some(instant.to_order()),
444 "the order key is the stored layout, so the projection must round-trip exactly"
445 );
446
447 let next_instant = GroupMeta {
450 high_water: Some(DateTime::from_bits(instant.to_bits() + 1)),
451 };
452 assert!(via_storage(&next_instant) > via_storage(&datetime_meta));
453 }
454
455 #[test]
456 fn a_group_that_never_advanced_projects_to_none_through_storage() {
457 let empty: GroupMeta<DateTime> = GroupMeta {
459 high_water: None,
460 };
461 assert_eq!(via_storage(&empty), None);
462 }
463}