reifydb_sub_flow/operator/window/
operator.rs1use reifydb_abi::operator::capabilities::OperatorCapability;
5use reifydb_codec::encoded::shape::RowShape;
6use reifydb_core::{
7 common::{CommitVersion, TimeDomain, WindowKind, WindowSize},
8 error::diagnostic::flow::{flow_window_timestamp_column_not_found, flow_window_timestamp_column_type_mismatch},
9 interface::{catalog::flow::FlowNodeId, change::Change},
10 value::column::columns::Columns,
11 window::engine::{LatePolicy, config::WindowEngineConfig},
12};
13use reifydb_engine::flow::aggregate::AggregateContext;
14use reifydb_routine::routine::registry::Routines;
15use reifydb_rql::expression::Expression;
16use reifydb_runtime::context::RuntimeContext;
17use reifydb_sdk::operator::Tick;
18use reifydb_value::{
19 Result,
20 error::Error,
21 value::{Value, datetime::DateTime, duration::Duration},
22};
23
24use super::{
25 aggregation::Aggregation,
26 rolling::{
27 apply_rolling_engine, apply_rolling_processing_engine, tick_expire_rolling_engine,
28 tick_expire_rolling_processing_engine,
29 },
30 tumbling::{
31 apply_session_engine, apply_sliding_engine, apply_tumbling_engine, tick_expire_engine_windows,
32 tick_expire_session_engine,
33 },
34};
35use crate::{
36 operator::{
37 Operator, OperatorCell,
38 stateful::{raw::RawStatefulOperator, row::RowNumberProvider, window::WindowStateful},
39 },
40 transaction::FlowTransaction,
41};
42
43pub struct WindowConfig {
44 pub parent: OperatorCell,
45 pub node: FlowNodeId,
46 pub kind: WindowKind,
47 pub group_by: Vec<Expression>,
48 pub aggregations: Vec<Expression>,
49 pub ts: Option<String>,
50 pub runtime_context: RuntimeContext,
51 pub routines: Routines,
52 pub late_policy: LatePolicy,
53 pub lateness: Option<Duration>,
54 pub state_cache_size: Option<usize>,
55 pub internal_state_cache_size: Option<usize>,
56}
57
58pub struct WindowOperator {
59 pub core: Aggregation,
60 pub kind: WindowKind,
61 pub ts: Option<String>,
62
63 pub late_policy: LatePolicy,
64 pub lateness: Option<Duration>,
65 pub state_cache_size: Option<usize>,
66 pub internal_state_cache_size: Option<usize>,
67 pub layout: RowShape,
68 pub row_number_provider: RowNumberProvider,
69}
70
71impl WindowOperator {
72 pub fn new(config: WindowConfig) -> Self {
73 let core = Aggregation::new(
74 config.node,
75 config.parent,
76 config.group_by,
77 config.aggregations,
78 config.routines,
79 config.runtime_context,
80 AggregateContext::Windowed,
81 );
82 Self {
83 core,
84 kind: config.kind,
85 ts: config.ts,
86 late_policy: config.late_policy,
87 lateness: config.lateness,
88 state_cache_size: config.state_cache_size,
89 internal_state_cache_size: config.internal_state_cache_size,
90 layout: RowShape::operator_state(),
91 row_number_provider: RowNumberProvider::new(config.node),
92 }
93 }
94
95 pub(crate) fn engine_config(&self) -> WindowEngineConfig {
96 let mut builder = WindowEngineConfig::builder().late_policy(self.late_policy);
97 if let Some(capacity) = self.state_cache_size {
98 builder = builder.state_cache_capacity(capacity);
99 }
100 if let Some(capacity) = self.internal_state_cache_size {
101 builder = builder.internal_state_cache_capacity(capacity);
102 }
103 builder.build()
104 }
105
106 pub fn is_count_based(&self) -> bool {
107 self.kind.size().is_some_and(|m| m.is_count())
108 }
109
110 pub fn sealing_lateness(&self) -> Option<Duration> {
111 match self.kind.time() {
112 TimeDomain::Event if !self.is_count_based() => self.lateness,
113 _ => None,
114 }
115 }
116
117 pub fn is_rolling(&self) -> bool {
118 matches!(self.kind, WindowKind::Rolling { .. })
119 }
120
121 pub fn size_duration(&self) -> Option<Duration> {
122 self.kind.size().and_then(|m| m.as_duration())
123 }
124
125 pub fn size_count(&self) -> Option<u64> {
126 self.kind.size().and_then(|m| m.as_count())
127 }
128
129 pub fn resolve_event_timestamps(&self, columns: &Columns, row_count: usize) -> Result<Vec<u64>> {
130 if row_count == 0 {
131 return Ok(Vec::new());
132 }
133 match (self.kind.time(), &self.ts) {
134 (TimeDomain::Event, Some(ts_col)) => {
135 let col = columns.column(ts_col).ok_or_else(|| {
136 Error(Box::new(flow_window_timestamp_column_not_found(ts_col)))
137 })?;
138 let mut timestamps = Vec::with_capacity(row_count);
139 for i in 0..row_count {
140 match col.data().get_value(i) {
141 Value::DateTime(dt) => timestamps.push(dt.timestamp_millis() as u64),
142 other => {
143 return Err(Error(Box::new(
144 flow_window_timestamp_column_type_mismatch(
145 ts_col,
146 other.get_type(),
147 ),
148 )));
149 }
150 }
151 }
152 Ok(timestamps)
153 }
154 _ => {
155 let now = self.core.current_timestamp();
156 Ok(vec![now; row_count])
157 }
158 }
159 }
160}
161
162impl RawStatefulOperator for WindowOperator {}
163
164impl WindowStateful for WindowOperator {
165 fn layout(&self) -> RowShape {
166 self.layout.clone()
167 }
168}
169
170impl Operator for WindowOperator {
171 fn id(&self) -> FlowNodeId {
172 self.core.node
173 }
174
175 fn capabilities(&self) -> &[OperatorCapability] {
176 OperatorCapability::STANDARD_WITH_TICK
177 }
178
179 fn ticks(&self) -> Option<Duration> {
180 match &self.kind {
181 WindowKind::Tumbling {
182 ..
183 }
184 | WindowKind::Sliding {
185 ..
186 }
187 | WindowKind::Session {
188 ..
189 }
190 | WindowKind::Rolling {
191 size: WindowSize::Duration(_),
192 ..
193 } => Some(Duration::from_seconds(1).unwrap()),
194 WindowKind::Rolling {
195 size: WindowSize::Count(_),
196 ..
197 } => None,
198 }
199 }
200
201 fn apply(&self, txn: &mut FlowTransaction, change: Change) -> Result<Change> {
202 match &self.kind {
203 WindowKind::Tumbling {
204 ..
205 } => apply_tumbling_engine(self, txn, change),
206 WindowKind::Sliding {
207 ..
208 } => apply_sliding_engine(self, txn, change),
209 WindowKind::Rolling {
210 ..
211 } => {
212 if self.is_rolling_processing() {
213 apply_rolling_processing_engine(self, txn, change)
214 } else {
215 apply_rolling_engine(self, txn, change)
216 }
217 }
218 WindowKind::Session {
219 ..
220 } => apply_session_engine(self, txn, change),
221 }
222 }
223
224 fn tick(&self, txn: &mut FlowTransaction, tick: Tick) -> Result<Option<Change>> {
225 let current_timestamp = tick.now.to_nanos() / 1_000_000;
226 let diffs = match &self.kind {
227 WindowKind::Tumbling {
228 ..
229 }
230 | WindowKind::Sliding {
231 ..
232 } => tick_expire_engine_windows(self, txn, current_timestamp)?,
233 WindowKind::Rolling {
234 size: WindowSize::Duration(_),
235 ..
236 } if self.is_rolling_processing() => tick_expire_rolling_processing_engine(self, txn, current_timestamp)?,
237 WindowKind::Rolling {
238 size: WindowSize::Duration(_),
239 ..
240 } => tick_expire_rolling_engine(self, txn, current_timestamp)?,
241 WindowKind::Session {
242 ..
243 } => tick_expire_session_engine(self, txn, current_timestamp)?,
244 _ => vec![],
245 };
246
247 if diffs.is_empty() {
248 Ok(None)
249 } else {
250 Ok(Some(Change::from_flow(
251 self.core.node,
252 CommitVersion(0),
253 diffs,
254 DateTime::from_nanos(self.core.runtime_context.clock.now_nanos()),
255 )))
256 }
257 }
258}