1use serde::{Deserialize, Serialize};
5
6use crate::{
7 common::{JoinType, TimeDomain},
8 expression::Expression,
9 interface::catalog::{
10 flow::OperatorId,
11 id::{RingBufferId, SeriesId, SubscriptionId, TableId, ViewId},
12 object::ObjectId,
13 series::SeriesKey,
14 },
15 operator_with::{AggregateWith, ApplyWith, DistinctWith, JoinWith, LookupWith, WindowWith},
16 sort::SortKey,
17};
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub enum OperatorDef {
21 SourceInlineData {},
22 SourceTable {
23 table: TableId,
24 time_domain: TimeDomain,
25 },
26 SourceView {
27 view: ViewId,
28 },
29 SourceRingBuffer {
30 ringbuffer: RingBufferId,
31 time_domain: TimeDomain,
32 },
33 SourceSeries {
34 series: SeriesId,
35 time_domain: TimeDomain,
36 },
37 Filter {
38 conditions: Vec<Expression>,
39 },
40 Gate {
41 conditions: Vec<Expression>,
42 },
43 Map {
44 expressions: Vec<Expression>,
45 },
46 Extend {
47 expressions: Vec<Expression>,
48 },
49 Join {
50 join_type: JoinType,
51 left: Vec<Expression>,
52 right: Vec<Expression>,
53 alias: Option<String>,
54 #[serde(default)]
55 natural: bool,
56 with: JoinWith,
57 },
58 Aggregate {
59 by: Vec<Expression>,
60 map: Vec<Expression>,
61 with: AggregateWith,
62 },
63 Append {},
64 Sort {
65 by: Vec<SortKey>,
66 },
67 Take {
68 limit: usize,
69 },
70 Distinct {
71 expressions: Vec<Expression>,
72 with: DistinctWith,
73 },
74 Apply {
75 operator: String,
76 params: Vec<Expression>,
77 with: ApplyWith,
78 },
79 SinkTableView {
80 view: ViewId,
81 },
82 SinkRingBufferView {
83 view: ViewId,
84 capacity: u64,
85 },
86 SinkSeriesView {
87 view: ViewId,
88 key: SeriesKey,
89 },
90 SinkSubscription {
91 subscription: SubscriptionId,
92 },
93 Window {
94 group_by: Vec<Expression>,
95 aggregations: Vec<Expression>,
96 with: WindowWith,
97 },
98 Lookup {
99 join_type: JoinType,
100 right: LookupObject,
101 left: Vec<Expression>,
102 alias: Option<String>,
103 with: LookupWith,
104 },
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
108pub enum LookupObject {
109 Table(TableId),
110 View(ViewId),
111 RingBuffer(RingBufferId),
112}
113
114impl OperatorDef {
115 pub fn is_source(&self) -> bool {
116 matches!(
117 self,
118 OperatorDef::SourceInlineData {}
119 | OperatorDef::SourceTable { .. }
120 | OperatorDef::SourceView { .. }
121 | OperatorDef::SourceRingBuffer { .. }
122 | OperatorDef::SourceSeries { .. }
123 )
124 }
125
126 pub fn declares_time(&self) -> bool {
127 match self {
128 OperatorDef::SourceTable {
129 time_domain,
130 ..
131 }
132 | OperatorDef::SourceRingBuffer {
133 time_domain,
134 ..
135 }
136 | OperatorDef::SourceSeries {
137 time_domain,
138 ..
139 } => *time_domain != TimeDomain::None,
140 OperatorDef::SourceView {
141 ..
142 } => true,
143 _ => false,
144 }
145 }
146
147 pub fn ticks(&self) -> bool {
148 matches!(
149 self,
150 OperatorDef::Append { .. }
151 | OperatorDef::Distinct { .. }
152 | OperatorDef::Window { .. } | OperatorDef::Apply { .. }
153 | OperatorDef::Join { .. } | OperatorDef::Lookup { .. }
154 | OperatorDef::Aggregate { .. }
155 | OperatorDef::SinkRingBufferView { .. }
156 )
157 }
158
159 pub fn label(&self) -> String {
160 match self {
161 OperatorDef::SourceInlineData {
162 ..
163 } => "SourceInlineData".into(),
164 OperatorDef::SourceTable {
165 ..
166 } => "SourceTable".into(),
167 OperatorDef::SourceView {
168 ..
169 } => "SourceView".into(),
170 OperatorDef::SourceRingBuffer {
171 ..
172 } => "SourceRingBuffer".into(),
173 OperatorDef::SourceSeries {
174 ..
175 } => "SourceSeries".into(),
176 OperatorDef::Filter {
177 ..
178 } => "Filter".into(),
179 OperatorDef::Gate {
180 ..
181 } => "Gate".into(),
182 OperatorDef::Map {
183 ..
184 } => "Map".into(),
185 OperatorDef::Extend {
186 ..
187 } => "Extend".into(),
188 OperatorDef::Join {
189 ..
190 } => "Join".into(),
191 OperatorDef::Aggregate {
192 ..
193 } => "Aggregate".into(),
194 OperatorDef::Append {
195 ..
196 } => "Append".into(),
197 OperatorDef::Sort {
198 ..
199 } => "Sort".into(),
200 OperatorDef::Take {
201 ..
202 } => "Take".into(),
203 OperatorDef::Distinct {
204 ..
205 } => "Distinct".into(),
206 OperatorDef::Apply {
207 operator,
208 ..
209 } => format!("Apply({})", operator),
210 OperatorDef::SinkTableView {
211 ..
212 } => "SinkTableView".into(),
213 OperatorDef::SinkRingBufferView {
214 ..
215 } => "SinkRingBufferView".into(),
216 OperatorDef::SinkSeriesView {
217 ..
218 } => "SinkSeriesView".into(),
219 OperatorDef::SinkSubscription {
220 ..
221 } => "SinkSubscription".into(),
222 OperatorDef::Window {
223 ..
224 } => "Window".into(),
225 OperatorDef::Lookup {
226 ..
227 } => "Lookup".into(),
228 }
229 }
230
231 pub fn discriminator(&self) -> u8 {
232 match self {
233 OperatorDef::SourceInlineData {
234 ..
235 } => 0,
236 OperatorDef::SourceTable {
237 ..
238 } => 1,
239 OperatorDef::SourceView {
240 ..
241 } => 2,
242 OperatorDef::Filter {
243 ..
244 } => 4,
245 OperatorDef::Map {
246 ..
247 } => 5,
248 OperatorDef::Extend {
249 ..
250 } => 6,
251 OperatorDef::Join {
252 ..
253 } => 7,
254 OperatorDef::Aggregate {
255 ..
256 } => 8,
257 OperatorDef::Append {
258 ..
259 } => 9,
260 OperatorDef::Sort {
261 ..
262 } => 10,
263 OperatorDef::Take {
264 ..
265 } => 11,
266 OperatorDef::Distinct {
267 ..
268 } => 12,
269 OperatorDef::Apply {
270 ..
271 } => 13,
272 OperatorDef::SinkSubscription {
273 ..
274 } => 14,
275 OperatorDef::Window {
276 ..
277 } => 15,
278 OperatorDef::SourceRingBuffer {
279 ..
280 } => 16,
281 OperatorDef::SourceSeries {
282 ..
283 } => 17,
284 OperatorDef::Gate {
285 ..
286 } => 18,
287 OperatorDef::SinkTableView {
288 ..
289 } => 19,
290 OperatorDef::SinkRingBufferView {
291 ..
292 } => 20,
293 OperatorDef::SinkSeriesView {
294 ..
295 } => 21,
296 OperatorDef::Lookup {
297 ..
298 } => 22,
299 }
300 }
301
302 pub fn source_object_id(&self) -> Option<ObjectId> {
303 match self {
304 OperatorDef::SourceTable {
305 table,
306 ..
307 } => Some(ObjectId::table(*table)),
308 OperatorDef::SourceRingBuffer {
309 ringbuffer,
310 ..
311 } => Some(ObjectId::ringbuffer(*ringbuffer)),
312 OperatorDef::SourceSeries {
313 series,
314 ..
315 } => Some(ObjectId::series(*series)),
316 OperatorDef::SourceInlineData {
317 ..
318 }
319 | OperatorDef::SourceView {
320 ..
321 }
322 | OperatorDef::Filter {
323 ..
324 }
325 | OperatorDef::Gate {
326 ..
327 }
328 | OperatorDef::Map {
329 ..
330 }
331 | OperatorDef::Extend {
332 ..
333 }
334 | OperatorDef::Join {
335 ..
336 }
337 | OperatorDef::Aggregate {
338 ..
339 }
340 | OperatorDef::Append {
341 ..
342 }
343 | OperatorDef::Sort {
344 ..
345 }
346 | OperatorDef::Take {
347 ..
348 }
349 | OperatorDef::Distinct {
350 ..
351 }
352 | OperatorDef::Apply {
353 ..
354 }
355 | OperatorDef::SinkTableView {
356 ..
357 }
358 | OperatorDef::SinkRingBufferView {
359 ..
360 }
361 | OperatorDef::SinkSeriesView {
362 ..
363 }
364 | OperatorDef::SinkSubscription {
365 ..
366 }
367 | OperatorDef::Window {
368 ..
369 }
370 | OperatorDef::Lookup {
371 ..
372 } => None,
373 }
374 }
375}
376
377#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
378pub struct FlowNode {
379 pub id: OperatorId,
380 pub ty: OperatorDef,
381 pub inputs: Vec<OperatorId>,
382 pub outputs: Vec<OperatorId>,
383}
384
385impl FlowNode {
386 pub fn new(id: impl Into<OperatorId>, ty: OperatorDef) -> Self {
387 Self {
388 id: id.into(),
389 ty,
390 inputs: Vec::new(),
391 outputs: Vec::new(),
392 }
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use postcard::{from_bytes, to_stdvec};
399 use reifydb_value::value::duration::Duration;
400
401 use super::{LookupObject, OperatorDef};
402 use crate::{
403 common::JoinType,
404 interface::catalog::id::{TableId, ViewId},
405 operator_with::{ApplyWith, DistinctWith, JoinWith, LookupWith},
406 row::OperatorRetention,
407 };
408
409 fn join() -> OperatorDef {
410 OperatorDef::Join {
411 join_type: JoinType::Inner,
412 left: vec![],
413 right: vec![],
414 alias: None,
415 natural: false,
416 with: JoinWith::default(),
417 }
418 }
419
420 #[test]
421 fn join_always_requests_ticks() {
422 assert!(join().ticks());
424 }
425
426 #[test]
427 fn apply_always_requests_ticks() {
428 let apply = OperatorDef::Apply {
431 operator: "compute_swap_volumes".to_string(),
432 params: vec![],
433 with: ApplyWith::default(),
434 };
435 assert!(apply.ticks());
436 }
437
438 #[test]
439 fn append_and_distinct_always_request_ticks() {
440 assert!(OperatorDef::Append {}.ticks());
442 assert!(OperatorDef::Distinct {
443 expressions: vec![],
444 with: DistinctWith {},
445 }
446 .ticks());
447 }
448
449 #[test]
450 fn sink_ringbuffer_view_always_requests_ticks() {
451 assert!(OperatorDef::SinkRingBufferView {
454 view: ViewId(1),
455 capacity: 1,
456 }
457 .ticks());
458 }
459
460 #[test]
461 fn stateless_nodes_do_not_request_ticks() {
462 assert!(!OperatorDef::Map {
463 expressions: vec![]
464 }
465 .ticks());
466 assert!(!OperatorDef::Filter {
467 conditions: vec![]
468 }
469 .ticks());
470 }
471
472 fn lookup(right: LookupObject) -> OperatorDef {
473 OperatorDef::Lookup {
474 join_type: JoinType::Left,
475 right,
476 left: vec![],
477 alias: Some("price".to_string()),
478 with: LookupWith {
479 retention: Some(OperatorRetention {
480 duration: Duration::from_seconds(10).unwrap(),
481 }),
482 },
483 }
484 }
485
486 #[test]
487 fn lookup_always_requests_ticks() {
488 assert!(lookup(LookupObject::Table(TableId(1))).ticks());
490 }
491
492 #[test]
493 fn lookup_is_not_a_source_and_has_no_source_object() {
494 let node = lookup(LookupObject::View(ViewId(7)));
496 assert!(!node.is_source());
497 assert!(node.source_object_id().is_none());
498 }
499
500 #[test]
501 fn lookup_discriminator_is_appended_after_every_existing_operator() {
502 let node = lookup(LookupObject::Table(TableId(1)));
504 assert_eq!(node.discriminator(), 22);
505 assert_eq!(node.label(), "Lookup");
506 }
507
508 #[test]
509 fn lookup_survives_the_persisted_encoding_with_its_right_object_and_retention() {
510 for right in [LookupObject::Table(TableId(3)), LookupObject::View(ViewId(4))] {
512 let node = lookup(right);
513 let bytes = to_stdvec(&node).expect("a lookup node must encode");
514 let decoded: OperatorDef = from_bytes(&bytes).expect("a lookup node must decode");
515 let OperatorDef::Lookup {
516 join_type,
517 right: decoded_right,
518 alias,
519 with,
520 ..
521 } = decoded
522 else {
523 panic!("a lookup must decode as a lookup, got {decoded:?}");
524 };
525 assert_eq!(join_type, JoinType::Left);
526 assert_eq!(decoded_right, right);
527 assert_eq!(alias.as_deref(), Some("price"));
528 assert_eq!(with.retention.map(|r| r.duration), Some(Duration::from_seconds(10).unwrap()));
529 }
530 }
531}