1use reifydb_core::interface::change::Change;
5#[cfg(feature = "runtime")]
6use reifydb_core::{
7 interface::{catalog::flow::OperatorId, flow::OperatorCapability},
8 metrics::heap::OperatorSample,
9 value::column::columns::Columns,
10};
11#[cfg(feature = "runtime")]
12use reifydb_value::Result;
13use reifydb_value::value::datetime::DateTime;
14#[cfg(any(feature = "runtime", all(reifydb_target = "host", not(reifydb_dst))))]
15use reifydb_value::value::duration::Duration;
16
17#[cfg(feature = "runtime")]
18use crate::{operator::host::HostContext, timer::Timer};
19
20#[cfg(all(reifydb_target = "host", not(reifydb_dst)))]
21pub fn scale_from_millis(span: Option<u64>) -> Option<Duration> {
22 span.filter(|millis| *millis > 0)
23 .and_then(|millis| i64::try_from(millis).ok())
24 .and_then(|millis| Duration::from_milliseconds(millis).ok())
25}
26
27#[cfg(feature = "runtime")]
28pub mod aggregation;
29#[cfg(feature = "runtime")]
30pub mod append;
31#[cfg(feature = "runtime")]
32pub mod apply;
33#[cfg(feature = "runtime")]
34pub mod distinct;
35#[cfg(feature = "runtime")]
36pub mod drops;
37#[cfg(feature = "runtime")]
38pub mod extend;
39#[cfg(feature = "runtime")]
40pub mod filter;
41#[cfg(feature = "runtime")]
42pub mod gate;
43#[cfg(feature = "runtime")]
44pub mod guard;
45#[cfg(feature = "runtime")]
46pub mod host;
47#[cfg(feature = "runtime")]
48pub mod join;
49#[cfg(feature = "runtime")]
50pub mod map;
51#[cfg(feature = "runtime")]
52pub mod metrics;
53#[cfg(feature = "runtime")]
54pub mod provider;
55#[cfg(feature = "runtime")]
56pub mod scan;
57#[cfg(feature = "runtime")]
58pub mod sink;
59#[cfg(feature = "runtime")]
60pub mod sort;
61pub mod state;
62pub mod state_access;
63#[cfg(feature = "runtime")]
64pub mod take;
65#[cfg(feature = "runtime")]
66pub mod window;
67
68#[cfg(feature = "runtime")]
69pub trait HostOperator: Send {
70 fn id(&self) -> OperatorId;
71
72 fn capabilities(&self) -> &[OperatorCapability];
73
74 fn apply(&mut self, host: &mut dyn HostContext, change: Change) -> Result<Change>;
75
76 fn on_timer(&mut self, _host: &mut dyn HostContext, _timer: Timer) -> Result<Option<Change>> {
77 Ok(None)
78 }
79
80 fn seal_span(&self) -> Option<Duration> {
81 None
82 }
83
84 fn sample(&self) -> Option<OperatorSample> {
85 None
86 }
87
88 fn output_schema(&self) -> Option<Columns> {
89 None
90 }
91}
92
93#[cfg(feature = "runtime")]
94pub type BoxedHostOperator = Box<dyn HostOperator>;
95
96pub fn max_input_time(change: &Change) -> Option<DateTime> {
97 change.diffs
98 .iter()
99 .filter_map(|diff| diff.post().or_else(|| diff.pre()))
100 .flat_map(|columns| columns.time().iter().copied())
101 .max()
102}
103
104#[cfg_attr(not(feature = "runtime"), allow(dead_code))]
105pub(crate) fn stamp_output_time(change: &mut Change, inherited: Option<DateTime>) {
106 let Some(inherited) = inherited else {
107 return;
108 };
109 for diff in change.diffs.iter_mut() {
110 for columns in diff.columns_mut() {
111 let stamped: Vec<DateTime> = columns.time().iter().map(|own| (*own).min(inherited)).collect();
112 columns.system.set_time(stamped);
113 }
114 }
115}
116
117#[cfg(test)]
118mod substrate_stamping_tests {
119 use reifydb_core::{
120 common::CommitVersion,
121 interface::{
122 catalog::flow::OperatorId,
123 change::{Diff, Diffs},
124 },
125 value::column::{ColumnWithName, buffer::ColumnBuffer, columns::Columns},
126 };
127 use reifydb_value::{
128 factory::time::at_millis,
129 fragment::Fragment,
130 value::{row_number::RowNumber, system_columns::SystemColumns},
131 };
132
133 use super::*;
134
135 fn columns(times: &[DateTime]) -> Columns {
136 let n = times.len();
137 Columns::with_system(
138 vec![ColumnWithName::new(
139 Fragment::internal("v"),
140 ColumnBuffer::int4((0..n as i32).collect::<Vec<_>>()),
141 )],
142 SystemColumns::new(
143 (1..=n as u64).map(RowNumber).collect(),
144 Vec::new(),
145 vec![at_millis(0); n],
146 vec![at_millis(0); n],
147 times.to_vec(),
148 ),
149 )
150 }
151
152 fn untimed_columns(n: usize) -> Columns {
153 Columns::with_system(
154 vec![ColumnWithName::new(
155 Fragment::internal("v"),
156 ColumnBuffer::int4((0..n as i32).collect::<Vec<_>>()),
157 )],
158 SystemColumns::new(
159 (1..=n as u64).map(RowNumber).collect(),
160 Vec::new(),
161 vec![at_millis(0); n],
162 vec![at_millis(0); n],
163 Vec::new(),
164 ),
165 )
166 }
167
168 fn change(diffs: Diffs) -> Change {
169 Change::from_flow(OperatorId(1), CommitVersion(1), diffs, at_millis(0))
170 }
171
172 #[test]
173 fn the_substrate_stamps_output_with_the_max_input_time() {
174 let mut diffs = Diffs::new();
177 diffs.push(Diff::insert(columns(&[at_millis(1_000), at_millis(9_000), at_millis(5_000)])));
178
179 assert_eq!(max_input_time(&change(diffs)), Some(at_millis(9_000)));
180 }
181
182 #[test]
183 fn an_operator_cannot_influence_its_own_output_time() {
184 let mut produced = Diffs::new();
189 produced.push(Diff::insert(columns(&[at_millis(999_999), at_millis(1_000)])));
190 let mut out = change(produced);
191
192 stamp_output_time(&mut out, Some(at_millis(4_000)));
193
194 assert_eq!(
195 out.diffs[0].post().unwrap().time().to_vec(),
196 vec![at_millis(4_000), at_millis(1_000)],
197 "a row above the inherited instant is pulled down; one below keeps its own"
198 );
199 }
200
201 #[test]
202 fn both_sides_of_an_update_are_stamped() {
203 let mut produced = Diffs::new();
206 produced.push(Diff::update(columns(&[at_millis(9_000)]), columns(&[at_millis(10_000)])));
207 let mut out = change(produced);
208
209 stamp_output_time(&mut out, Some(at_millis(7_000)));
210
211 assert_eq!(out.diffs[0].pre().unwrap().time().to_vec(), vec![at_millis(7_000)]);
212 assert_eq!(out.diffs[0].post().unwrap().time().to_vec(), vec![at_millis(7_000)]);
213 }
214
215 #[test]
216 fn a_fan_out_operator_has_every_emitted_row_stamped() {
217 let mut produced = Diffs::new();
220 produced.push(Diff::insert(columns(&[
221 at_millis(9_000),
222 at_millis(10_000),
223 at_millis(11_000),
224 at_millis(12_000),
225 at_millis(13_000),
226 ])));
227 let mut out = change(produced);
228
229 stamp_output_time(&mut out, Some(at_millis(8_000)));
230
231 assert_eq!(out.diffs[0].post().unwrap().time().to_vec(), vec![at_millis(8_000); 5]);
232 }
233
234 #[test]
235 fn an_empty_input_leaves_the_output_untouched() {
236 let empty = change(Diffs::new());
239 assert_eq!(max_input_time(&empty), None);
240
241 let mut produced = Diffs::new();
242 produced.push(Diff::insert(columns(&[at_millis(3_000)])));
243 let mut out = change(produced);
244
245 stamp_output_time(&mut out, None);
246
247 assert_eq!(out.diffs[0].post().unwrap().time().to_vec(), vec![at_millis(3_000)]);
248 }
249
250 #[test]
251 fn an_operator_stamping_above_its_inputs_is_still_overwritten() {
252 let inherited = at_millis(5_000);
255 let one_nano_above = DateTime::from_nanos(inherited.to_nanos() + 1);
256
257 let mut produced = Diffs::new();
258 produced.push(Diff::insert(columns(&[one_nano_above])));
259 let mut out = change(produced);
260
261 stamp_output_time(&mut out, Some(inherited));
262
263 assert_eq!(out.diffs[0].post().unwrap().time().to_vec(), vec![inherited]);
264 }
265
266 #[test]
267 fn an_operator_stamping_at_or_below_its_inputs_keeps_its_stamp() {
268 let inherited = at_millis(5_000);
272
273 let mut produced = Diffs::new();
274 produced.push(Diff::insert(columns(&[at_millis(1_000), inherited])));
275 let mut out = change(produced);
276
277 stamp_output_time(&mut out, Some(inherited));
278
279 assert_eq!(
280 out.diffs[0].post().unwrap().time().to_vec(),
281 vec![at_millis(1_000), inherited],
282 "below survives, and equal counts as below"
283 );
284 }
285
286 #[test]
287 fn a_row_stamped_at_the_epoch_keeps_its_own_instant() {
288 let mut produced = Diffs::new();
292 produced.push(Diff::insert(columns(&[DateTime::default()])));
293 let mut out = change(produced);
294
295 stamp_output_time(&mut out, Some(at_millis(6_000)));
296
297 assert_eq!(out.diffs[0].post().unwrap().time().to_vec(), vec![DateTime::default()]);
298 }
299
300 #[test]
301 fn a_time_less_batch_stays_time_less_through_stamping() {
302 let mut produced = Diffs::new();
306 produced.push(Diff::insert(untimed_columns(3)));
307 let mut out = change(produced);
308
309 stamp_output_time(&mut out, Some(at_millis(6_000)));
310
311 assert!(out.diffs[0].post().unwrap().time().is_empty(), "#time must stay absent");
312 }
313
314 #[test]
315 fn a_window_row_carries_its_window_start_through_the_apply_wrapper() {
316 let window_start = at_millis(60_000);
320 let newest_event_in_bucket = at_millis(119_000);
321 let seal_fires_at = at_millis(120_001);
322
323 let mut on_apply = change({
324 let mut d = Diffs::new();
325 d.push(Diff::insert(columns(&[window_start])));
326 d
327 });
328 stamp_output_time(&mut on_apply, Some(newest_event_in_bucket));
329 assert_eq!(on_apply.diffs[0].post().unwrap().time().to_vec(), vec![window_start]);
330
331 let mut on_timer = change({
332 let mut d = Diffs::new();
333 d.push(Diff::insert(columns(&[window_start])));
334 d
335 });
336 stamp_output_time(&mut on_timer, Some(seal_fires_at));
337 assert_eq!(on_timer.diffs[0].post().unwrap().time().to_vec(), vec![window_start]);
338 }
339
340 #[test]
341 fn the_max_spans_every_diff_in_the_batch() {
342 let mut diffs = Diffs::new();
345 diffs.push(Diff::insert(columns(&[at_millis(1_000)])));
346 diffs.push(Diff::insert(columns(&[at_millis(12_000)])));
347 diffs.push(Diff::insert(columns(&[at_millis(3_000)])));
348
349 assert_eq!(max_input_time(&change(diffs)), Some(at_millis(12_000)));
350 }
351}