reifydb_flow/window/
coord.rs1use std::fmt::Debug;
5
6use reifydb_core::{
7 metrics::heap::HeapSize,
8 state::timer::{StateStore, TimerStore},
9};
10use reifydb_macro::operator_state;
11use reifydb_value::{
12 Result,
13 value::{datetime::DateTime, duration::Duration, row_number::RowNumber},
14};
15
16use crate::{
17 operator::state::seal::{
18 coord::{Coord, IsZero},
19 domain::SealDomain,
20 ledger::SealLedger,
21 },
22 window::span::Slot,
23};
24
25pub trait TimeStamped {
26 fn row_time(&self) -> DateTime;
27}
28
29impl TimeStamped for DateTime {
30 fn row_time(&self) -> DateTime {
31 *self
32 }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub struct EventCoord(DateTime);
37
38impl EventCoord {
39 pub fn of(row: &impl TimeStamped) -> Self {
40 Self(row.row_time())
41 }
42
43 pub fn at(self) -> DateTime {
44 self.0
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
49pub struct RowSpan {
50 rows: u64,
51}
52
53impl RowSpan {
54 pub const ZERO: Self = Self {
55 rows: 0,
56 };
57
58 pub fn of(rows: u64) -> Self {
59 Self {
60 rows,
61 }
62 }
63
64 pub fn rows(self) -> u64 {
65 self.rows
66 }
67}
68
69impl IsZero for RowSpan {
70 #[inline]
71 fn is_zero(&self) -> bool {
72 self.rows == 0
73 }
74}
75
76#[operator_state]
77#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
78pub struct OrdinalCoord {
79 ordinal: u64,
80}
81
82impl OrdinalCoord {
83 pub fn from_arrival_counter(ordinal: u64) -> Self {
84 Self {
85 ordinal,
86 }
87 }
88
89 pub fn from_row_number(row_number: RowNumber) -> Self {
90 Self {
91 ordinal: row_number.0,
92 }
93 }
94
95 pub fn value(self) -> u64 {
96 self.ordinal
97 }
98}
99
100impl HeapSize for OrdinalCoord {
101 fn heap_size(&self) -> usize {
102 0
103 }
104}
105
106impl Coord for OrdinalCoord {
107 type Span = RowSpan;
108
109 const MAX: Self = Self {
110 ordinal: u64::MAX,
111 };
112
113 fn saturating_sub_span(self, span: RowSpan) -> Self {
114 Self {
115 ordinal: self.ordinal.saturating_sub(span.rows),
116 }
117 }
118
119 fn checked_sub_span(self, span: RowSpan) -> Option<Self> {
120 self.ordinal.checked_sub(span.rows).map(|ordinal| Self {
121 ordinal,
122 })
123 }
124
125 fn add_span(self, span: RowSpan) -> Self {
126 Self {
127 ordinal: self.ordinal + span.rows,
128 }
129 }
130
131 fn floor_to(self, span: RowSpan) -> Self {
132 Self {
133 ordinal: self.ordinal - (self.ordinal % span.rows),
134 }
135 }
136
137 fn span_since(self, earlier: Self) -> RowSpan {
138 RowSpan {
139 rows: self.ordinal - earlier.ordinal,
140 }
141 }
142
143 fn to_order(self) -> u64 {
144 self.ordinal
145 }
146
147 fn from_order(order: u64) -> Self {
148 Self {
149 ordinal: order,
150 }
151 }
152
153 fn span_millis(_span: RowSpan) -> Option<u64> {
154 None
155 }
156}
157
158impl SealDomain for OrdinalCoord {
159 type Lateness = RowSpan;
160
161 fn arms_timer() -> bool {
162 false
163 }
164
165 fn lateness_duration(_lateness: RowSpan) -> Option<Duration> {
166 None
167 }
168
169 fn observe(store: &mut (impl StateStore + TimerStore), newest: Self, _lateness: RowSpan) -> Result<()> {
170 SealLedger::observe(store, newest.to_order())?;
171 Ok(())
172 }
173
174 fn frontier(store: &mut (impl StateStore + TimerStore)) -> Result<Self> {
175 Ok(Self::from_order(SealLedger::read_order(store)?.unwrap_or(0)))
176 }
177
178 fn horizon(frontier: Self, lateness: RowSpan) -> Self {
179 frontier.saturating_sub_span(lateness)
180 }
181}
182
183impl Slot for OrdinalCoord {
184 type Coord = OrdinalCoord;
185
186 fn order_key(&self) -> OrdinalCoord {
187 *self
188 }
189
190 fn from_order_key(coord: OrdinalCoord) -> Self {
191 coord
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use reifydb_codec::row::operator::state::encode;
198
199 use super::*;
200 use crate::operator::state::mock::MockStore;
201
202 struct Row {
203 time: DateTime,
204 other_column: DateTime,
205 }
206
207 impl TimeStamped for Row {
208 fn row_time(&self) -> DateTime {
209 self.time
210 }
211 }
212
213 #[test]
214 fn an_event_coordinate_can_only_come_from_the_row_time() {
215 let row = Row {
219 time: DateTime::from_millis(5_000),
220 other_column: DateTime::from_millis(9_999),
221 };
222
223 assert_eq!(EventCoord::of(&row).at(), DateTime::from_millis(5_000));
224 assert_ne!(EventCoord::of(&row).at(), row.other_column);
225 }
226
227 #[test]
228 fn event_coordinates_order_by_instant() {
229 let early = EventCoord::of(&DateTime::from_millis(1));
232 let late = EventCoord::of(&DateTime::from_millis(2));
233
234 assert!(early < late);
235 }
236
237 #[test]
238 fn an_ordinal_encodes_to_the_same_bytes_as_the_bare_count_it_replaced() {
239 let value = 0x0123_4567_89AB_CDEFu64;
241
242 let wrapped = encode(&OrdinalCoord::from_arrival_counter(value)).expect("encode");
243 let bare = encode(&value).expect("encode");
244
245 assert_eq!(wrapped.body(), bare.body(), "the newtype changed the persisted layout");
246 }
247
248 #[test]
249 fn ordinal_arithmetic_counts_rows_and_refuses_to_answer_in_milliseconds() {
250 let coord = OrdinalCoord::from_arrival_counter(100);
253
254 assert_eq!(coord.saturating_sub_span(RowSpan::of(64)), OrdinalCoord::from_arrival_counter(36));
255 assert_eq!(coord.add_span(RowSpan::of(5)), OrdinalCoord::from_arrival_counter(105));
256 assert_eq!(coord.span_since(OrdinalCoord::from_arrival_counter(60)), RowSpan::of(40));
257 assert_eq!(<OrdinalCoord as Coord>::span_millis(RowSpan::of(64)), None);
258 }
259
260 #[test]
261 fn an_ordinal_below_its_own_span_has_no_earlier_coordinate_rather_than_wrapping() {
262 let coord = OrdinalCoord::from_arrival_counter(10);
264
265 assert_eq!(coord.checked_sub_span(RowSpan::of(11)), None);
266 assert_eq!(coord.checked_sub_span(RowSpan::of(10)), Some(OrdinalCoord::from_arrival_counter(0)));
267 assert_eq!(coord.saturating_sub_span(RowSpan::of(11)), OrdinalCoord::from_arrival_counter(0));
268 }
269
270 #[test]
271 fn a_row_ordinal_seals_without_the_wheel_and_declares_no_wall_clock_lateness() {
272 assert!(!<OrdinalCoord as SealDomain>::arms_timer());
274 assert_eq!(<OrdinalCoord as SealDomain>::lateness_duration(RowSpan::of(64)), None);
275 }
276
277 #[test]
278 fn an_ordinal_frontier_moves_inline_from_the_batch_and_only_forward() {
279 let mut store = MockStore::default();
281
282 assert_eq!(OrdinalCoord::frontier(&mut store).unwrap(), OrdinalCoord::from_arrival_counter(0));
283
284 OrdinalCoord::observe(&mut store, OrdinalCoord::from_arrival_counter(90), RowSpan::ZERO).unwrap();
285 OrdinalCoord::observe(&mut store, OrdinalCoord::from_arrival_counter(30), RowSpan::ZERO).unwrap();
286
287 assert_eq!(OrdinalCoord::frontier(&mut store).unwrap(), OrdinalCoord::from_arrival_counter(90));
288 assert_eq!(
289 OrdinalCoord::horizon(OrdinalCoord::from_arrival_counter(90), RowSpan::of(64)),
290 OrdinalCoord::from_arrival_counter(26)
291 );
292 }
293
294 #[test]
295 fn both_ordinal_sources_produce_the_same_domain() {
296 let minted = OrdinalCoord::from_arrival_counter(7);
299 let from_row = OrdinalCoord::from_row_number(RowNumber(7));
300
301 assert_eq!(minted, from_row);
302 assert_eq!(minted.value(), 7);
303 }
304}