reifydb_flow/window/kind/
sliding.rs1use reifydb_value::value::{datetime::DateTime, duration::Duration};
5
6use crate::{
7 operator::state::seal::coord::Coord,
8 window::{
9 coord::{EventCoord, OrdinalCoord, RowSpan},
10 kind::ordinal_window_span,
11 span::WindowSpan,
12 },
13};
14
15fn fits(size: u64, slide: u64) -> Option<(u64, u64)> {
16 (size > 0 && slide > 0 && slide < size).then_some((size, slide))
17}
18
19fn fits_span(size: Duration, slide: Duration) -> Option<(Duration, Duration)> {
20 (size.is_positive() && slide.is_positive() && slide < size).then_some((size, slide))
21}
22
23pub struct SlidingOverTime {
24 size: Duration,
25 slide: Duration,
26}
27
28impl SlidingOverTime {
29 pub fn by_duration(size: Duration, slide: Duration) -> Option<Self> {
30 let (size, slide) = fits_span(size, slide)?;
31 Some(Self {
32 size,
33 slide,
34 })
35 }
36
37 pub fn span(&self, anchor: u64) -> WindowSpan<DateTime> {
38 let start = <DateTime as Coord>::from_order(anchor);
39 WindowSpan::new(start, start.saturating_add(self.size))
40 }
41
42 pub fn anchors(&self, coord: EventCoord) -> Vec<u64> {
43 let instant = coord.at();
44 let mut start = instant.saturating_sub(self.size).floor_to(self.slide);
45 let mut anchors = Vec::new();
46 while start <= instant {
47 if instant < start.saturating_add(self.size) {
48 anchors.push(start.to_order());
49 }
50 start = start.saturating_add(self.slide);
51 }
52 anchors
53 }
54}
55
56pub struct SlidingOverRows {
57 size: RowSpan,
58 slide: RowSpan,
59}
60
61impl SlidingOverRows {
62 pub fn by_count(size: RowSpan, slide: RowSpan) -> Option<Self> {
63 let (size, slide) = fits(size.rows(), slide.rows())?;
64 Some(Self {
65 size: RowSpan::of(size),
66 slide: RowSpan::of(slide),
67 })
68 }
69
70 pub fn span(&self, anchor: u64) -> WindowSpan<DateTime> {
71 ordinal_window_span(anchor)
72 }
73
74 pub fn anchors(&self, coord: OrdinalCoord) -> Vec<u64> {
75 let row = coord.value() + 1;
76 let size = self.size.rows();
77 let slide = self.slide.rows();
78 let lowest = if row > size {
79 (row - size) / slide
80 } else {
81 0
82 };
83 let highest = (row - 1) / slide;
84 (lowest..=highest)
85 .filter(|window| {
86 let first = window * slide + 1;
87 row >= first && row < first + size
88 })
89 .collect()
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use crate::factory::coord::event_coord_at_millis;
97
98 fn ms(millis: u64) -> Duration {
99 Duration::from_milliseconds_const(millis as i64)
100 }
101
102 fn order(millis: u64) -> u64 {
103 DateTime::from_millis(millis).to_order()
104 }
105
106 fn timed() -> SlidingOverTime {
107 SlidingOverTime::by_duration(ms(1_000), ms(250)).expect("a 250ms slide fits inside a 1000ms window")
108 }
109
110 fn counted() -> SlidingOverRows {
111 SlidingOverRows::by_count(RowSpan::of(4), RowSpan::of(2))
112 .expect("a slide of 2 fits inside a window of 4")
113 }
114
115 #[test]
116 fn a_zero_slide_cannot_be_constructed_at_all() {
117 assert!(SlidingOverTime::by_duration(ms(1_000), ms(0)).is_none());
121 assert!(SlidingOverRows::by_count(RowSpan::of(4), RowSpan::of(0)).is_none());
122 }
123
124 #[test]
125 fn a_slide_that_does_not_fit_inside_the_window_is_refused() {
126 assert!(SlidingOverTime::by_duration(ms(1_000), ms(1_000)).is_none());
129 assert!(SlidingOverRows::by_count(RowSpan::of(4), RowSpan::of(9)).is_none());
130 assert!(SlidingOverRows::by_count(RowSpan::of(0), RowSpan::of(0)).is_none());
131 }
132
133 #[test]
134 fn a_time_coordinate_lands_in_every_window_whose_span_still_covers_it() {
135 assert_eq!(
138 timed().anchors(event_coord_at_millis(5_000)),
139 vec![order(4_250), order(4_500), order(4_750), order(5_000)]
140 );
141 }
142
143 #[test]
144 fn the_earliest_instants_do_not_produce_windows_that_start_before_zero() {
145 assert_eq!(timed().anchors(event_coord_at_millis(0)), vec![order(0)]);
149 assert_eq!(timed().anchors(event_coord_at_millis(250)), vec![order(0), order(250)]);
150 }
151
152 #[test]
153 fn a_row_ordinal_lands_in_every_window_still_accepting_rows() {
154 assert_eq!(counted().anchors(OrdinalCoord::from_arrival_counter(0)), vec![0]);
158 assert_eq!(counted().anchors(OrdinalCoord::from_arrival_counter(3)), vec![0, 1]);
159 assert_eq!(counted().anchors(OrdinalCoord::from_arrival_counter(4)), vec![1, 2]);
160 }
161
162 #[test]
163 fn no_coordinate_ever_lands_in_zero_windows() {
164 for instant in (0..4_000).step_by(37) {
167 assert!(
168 !timed().anchors(event_coord_at_millis(instant)).is_empty(),
169 "instant {instant} joined no window"
170 );
171 }
172 for ordinal in 0..500 {
173 assert!(
174 !counted().anchors(OrdinalCoord::from_arrival_counter(ordinal)).is_empty(),
175 "ordinal {ordinal} joined no window"
176 );
177 }
178 }
179
180 #[test]
181 fn a_time_window_span_covers_exactly_the_size_it_was_built_with() {
182 let span = timed().span(order(4_250));
186
187 assert_eq!(span.start, DateTime::from_millis(4_250));
188 assert_eq!(span.end, DateTime::from_millis(5_250));
189 }
190}