Skip to main content

reifydb_flow/window/
coord.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::fmt::Debug;
5
6use reifydb_core::metrics::heap::HeapSize;
7use reifydb_macro::operator_state;
8use reifydb_value::value::{datetime::DateTime, row_number::RowNumber};
9
10use crate::{
11	operator::state::seal::coord::{Coord, IsZero},
12	window::span::Slot,
13};
14
15pub trait TimeStamped {
16	fn row_time(&self) -> DateTime;
17}
18
19impl TimeStamped for DateTime {
20	fn row_time(&self) -> DateTime {
21		*self
22	}
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
26pub struct EventCoord(DateTime);
27
28impl EventCoord {
29	pub fn of(row: &impl TimeStamped) -> Self {
30		Self(row.row_time())
31	}
32
33	pub fn at(self) -> DateTime {
34		self.0
35	}
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
39pub struct RowSpan {
40	rows: u64,
41}
42
43impl RowSpan {
44	pub const ZERO: Self = Self {
45		rows: 0,
46	};
47
48	pub fn of(rows: u64) -> Self {
49		Self {
50			rows,
51		}
52	}
53
54	pub fn rows(self) -> u64 {
55		self.rows
56	}
57}
58
59impl IsZero for RowSpan {
60	#[inline]
61	fn is_zero(&self) -> bool {
62		self.rows == 0
63	}
64}
65
66#[operator_state]
67#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
68pub struct OrdinalCoord {
69	ordinal: u64,
70}
71
72impl OrdinalCoord {
73	pub fn from_arrival_counter(ordinal: u64) -> Self {
74		Self {
75			ordinal,
76		}
77	}
78
79	pub fn from_row_number(row_number: RowNumber) -> Self {
80		Self {
81			ordinal: row_number.0,
82		}
83	}
84
85	pub fn value(self) -> u64 {
86		self.ordinal
87	}
88}
89
90impl HeapSize for OrdinalCoord {
91	fn heap_size(&self) -> usize {
92		0
93	}
94}
95
96impl Coord for OrdinalCoord {
97	type Span = RowSpan;
98
99	const MAX: Self = Self {
100		ordinal: u64::MAX,
101	};
102
103	fn saturating_sub_span(self, span: RowSpan) -> Self {
104		Self {
105			ordinal: self.ordinal.saturating_sub(span.rows),
106		}
107	}
108
109	fn checked_sub_span(self, span: RowSpan) -> Option<Self> {
110		self.ordinal.checked_sub(span.rows).map(|ordinal| Self {
111			ordinal,
112		})
113	}
114
115	fn add_span(self, span: RowSpan) -> Self {
116		Self {
117			ordinal: self.ordinal + span.rows,
118		}
119	}
120
121	fn floor_to(self, span: RowSpan) -> Self {
122		Self {
123			ordinal: self.ordinal - (self.ordinal % span.rows),
124		}
125	}
126
127	fn span_since(self, earlier: Self) -> RowSpan {
128		RowSpan {
129			rows: self.ordinal - earlier.ordinal,
130		}
131	}
132
133	fn to_order(self) -> u64 {
134		self.ordinal
135	}
136
137	fn from_order(order: u64) -> Self {
138		Self {
139			ordinal: order,
140		}
141	}
142
143	fn span_millis(_span: RowSpan) -> Option<u64> {
144		None
145	}
146}
147
148impl Slot for OrdinalCoord {
149	type Coord = OrdinalCoord;
150
151	fn order_key(&self) -> OrdinalCoord {
152		*self
153	}
154
155	fn from_order_key(coord: OrdinalCoord) -> Self {
156		coord
157	}
158}
159
160#[cfg(test)]
161mod tests {
162	use reifydb_codec::row::operator::encode;
163
164	use super::*;
165
166	struct Row {
167		time: DateTime,
168		other_column: DateTime,
169	}
170
171	impl TimeStamped for Row {
172		fn row_time(&self) -> DateTime {
173			self.time
174		}
175	}
176
177	#[test]
178	fn an_event_coordinate_can_only_come_from_the_row_time() {
179		// `of` is the only constructor, so a coordinate cannot come from a data column, a config
180		// value or a clock read. The row's second DateTime is what an operator would be tempted to
181		// bucket by, keying the window on something the substrate can neither see nor seal against.
182		let row = Row {
183			time: DateTime::from_millis(5_000),
184			other_column: DateTime::from_millis(9_999),
185		};
186
187		assert_eq!(EventCoord::of(&row).at(), DateTime::from_millis(5_000));
188		assert_ne!(EventCoord::of(&row).at(), row.other_column);
189	}
190
191	#[test]
192	fn event_coordinates_order_by_instant() {
193		// Ordering is what the seal ledger and the admissible-span comparison are built
194		// on, so it must be the instant's order and nothing else.
195		let early = EventCoord::of(&DateTime::from_millis(1));
196		let late = EventCoord::of(&DateTime::from_millis(2));
197
198		assert!(early < late);
199	}
200
201	#[test]
202	fn an_ordinal_encodes_to_the_same_bytes_as_the_bare_count_it_replaced() {
203		// A changed persisted layout is silent: stored buffer keys get reinterpreted, not rejected.
204		let value = 0x0123_4567_89AB_CDEFu64;
205
206		let wrapped = encode(&OrdinalCoord::from_arrival_counter(value), DateTime::EPOCH).expect("encode");
207		let bare = encode(&value, DateTime::EPOCH).expect("encode");
208
209		assert_eq!(wrapped.body(), bare.body(), "the newtype changed the persisted layout");
210	}
211
212	#[test]
213	fn ordinal_arithmetic_counts_rows_and_refuses_to_answer_in_milliseconds() {
214		// A span here is rows, not milliseconds. span_millis answering Some would let a row count
215		// reach the seal horizon as a duration.
216		let coord = OrdinalCoord::from_arrival_counter(100);
217
218		assert_eq!(coord.saturating_sub_span(RowSpan::of(64)), OrdinalCoord::from_arrival_counter(36));
219		assert_eq!(coord.add_span(RowSpan::of(5)), OrdinalCoord::from_arrival_counter(105));
220		assert_eq!(coord.span_since(OrdinalCoord::from_arrival_counter(60)), RowSpan::of(40));
221		assert_eq!(<OrdinalCoord as Coord>::span_millis(RowSpan::of(64)), None);
222	}
223
224	#[test]
225	fn an_ordinal_below_its_own_span_has_no_earlier_coordinate_rather_than_wrapping() {
226		// Wrapping below zero lands near u64::MAX and evicts the whole buffer on the first pass.
227		let coord = OrdinalCoord::from_arrival_counter(10);
228
229		assert_eq!(coord.checked_sub_span(RowSpan::of(11)), None);
230		assert_eq!(coord.checked_sub_span(RowSpan::of(10)), Some(OrdinalCoord::from_arrival_counter(0)));
231		assert_eq!(coord.saturating_sub_span(RowSpan::of(11)), OrdinalCoord::from_arrival_counter(0));
232	}
233
234	#[test]
235	fn both_ordinal_sources_produce_the_same_domain() {
236		// An ordinal can be minted from a per-group arrival counter or from a RowNumber, and both
237		// must land in one domain type or each count kind would need its own driver.
238		let minted = OrdinalCoord::from_arrival_counter(7);
239		let from_row = OrdinalCoord::from_row_number(RowNumber(7));
240
241		assert_eq!(minted, from_row);
242		assert_eq!(minted.value(), 7);
243	}
244}