1use std::{
5 fmt,
6 fmt::{Display, Formatter},
7 num::ParseIntError,
8 str::FromStr,
9};
10
11use reifydb_value::{Result as ValueResult, value::duration::Duration};
12use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor};
13
14use crate::{error::CoreError, operator_with::ApplyWith};
15
16#[repr(transparent)]
17#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)]
18pub struct CommitVersion(pub u64);
19
20impl FromStr for CommitVersion {
21 type Err = ParseIntError;
22
23 fn from_str(s: &str) -> Result<Self, Self::Err> {
24 Ok(CommitVersion(u64::from_str(s)?))
25 }
26}
27
28impl Display for CommitVersion {
29 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
30 Display::fmt(&self.0, f)
31 }
32}
33
34impl PartialEq<i32> for CommitVersion {
35 fn eq(&self, other: &i32) -> bool {
36 self.0 == *other as u64
37 }
38}
39
40impl PartialEq<CommitVersion> for i32 {
41 fn eq(&self, other: &CommitVersion) -> bool {
42 *self as u64 == other.0
43 }
44}
45
46impl PartialEq<u64> for CommitVersion {
47 fn eq(&self, other: &u64) -> bool {
48 self.0.eq(other)
49 }
50}
51
52impl From<CommitVersion> for u64 {
53 fn from(value: CommitVersion) -> Self {
54 value.0
55 }
56}
57
58impl From<i32> for CommitVersion {
59 fn from(value: i32) -> Self {
60 Self(value as u64)
61 }
62}
63
64impl From<u64> for CommitVersion {
65 fn from(value: u64) -> Self {
66 Self(value)
67 }
68}
69
70impl Serialize for CommitVersion {
71 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
72 where
73 S: Serializer,
74 {
75 serializer.serialize_u64(self.0)
76 }
77}
78
79impl<'de> Deserialize<'de> for CommitVersion {
80 fn deserialize<D>(deserializer: D) -> Result<CommitVersion, D::Error>
81 where
82 D: Deserializer<'de>,
83 {
84 struct U64Visitor;
85
86 impl Visitor<'_> for U64Visitor {
87 type Value = CommitVersion;
88
89 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
90 formatter.write_str("an unsigned 64-bit number")
91 }
92
93 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> {
94 Ok(CommitVersion(value))
95 }
96 }
97
98 deserializer.deserialize_u64(U64Visitor)
99 }
100}
101
102#[repr(transparent)]
103#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash, Serialize, Deserialize)]
104#[serde(transparent)]
105pub struct SourceVersion(pub u64);
106
107impl From<CommitVersion> for SourceVersion {
108 fn from(version: CommitVersion) -> Self {
109 Self(version.0)
110 }
111}
112
113#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
114pub struct ChangeVersion {
115 pub commit: CommitVersion,
116 pub source: SourceVersion,
117}
118
119impl From<CommitVersion> for ChangeVersion {
120 fn from(commit: CommitVersion) -> Self {
121 Self {
122 commit,
123 source: SourceVersion::from(commit),
124 }
125 }
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
129pub enum JoinType {
130 Inner,
131 #[default]
132 Left,
133}
134
135#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
136pub enum IndexType {
137 #[default]
138 Index,
139 Unique,
140 Primary,
141}
142
143#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
144pub enum WindowSize {
145 Duration(Duration),
146 Count(u64),
147}
148
149impl WindowSize {
150 pub fn is_count(&self) -> bool {
151 matches!(self, WindowSize::Count(_))
152 }
153
154 pub fn as_duration(&self) -> Option<Duration> {
155 match self {
156 WindowSize::Duration(d) => Some(*d),
157 _ => None,
158 }
159 }
160
161 pub fn as_count(&self) -> Option<u64> {
162 match self {
163 WindowSize::Count(c) => Some(*c),
164 _ => None,
165 }
166 }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
170pub enum TimeDomain {
171 None,
172 Event,
173 Processing,
174}
175
176impl TimeDomain {
177 pub fn to_u8(self) -> u8 {
178 match self {
179 TimeDomain::None => 0,
180 TimeDomain::Event => 1,
181 TimeDomain::Processing => 2,
182 }
183 }
184
185 pub fn from_u8(value: u8) -> Self {
186 match value {
187 1 => TimeDomain::Event,
188 2 => TimeDomain::Processing,
189 _ => TimeDomain::None,
190 }
191 }
192
193 pub fn as_str(&self) -> &'static str {
194 match self {
195 TimeDomain::None => "none",
196 TimeDomain::Event => "event",
197 TimeDomain::Processing => "processing",
198 }
199 }
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub enum OperatorClass {
204 Managed,
205 Unmanaged,
206 Nostate,
207 Windowed,
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub enum WindowSizeDomain {
212 Time,
213 Slots,
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub struct WindowRequirements {
218 pub takes_window: bool,
219 pub kinds: &'static [&'static str],
220 pub domain: WindowSizeDomain,
221 pub needs_pane: bool,
222}
223
224impl OperatorClass {
225 pub fn to_u8(self) -> u8 {
226 match self {
227 OperatorClass::Managed => 1,
228 OperatorClass::Unmanaged => 2,
229 OperatorClass::Nostate => 3,
230 OperatorClass::Windowed => 4,
231 }
232 }
233
234 pub fn from_u8(value: u8) -> Option<Self> {
235 match value {
236 1 => Some(OperatorClass::Managed),
237 2 => Some(OperatorClass::Unmanaged),
238 3 => Some(OperatorClass::Nostate),
239 4 => Some(OperatorClass::Windowed),
240 _ => None,
241 }
242 }
243}
244
245impl WindowSizeDomain {
246 pub fn to_u8(self) -> u8 {
247 match self {
248 WindowSizeDomain::Time => 1,
249 WindowSizeDomain::Slots => 2,
250 }
251 }
252
253 pub fn from_u8(value: u8) -> Option<Self> {
254 match value {
255 1 => Some(WindowSizeDomain::Time),
256 2 => Some(WindowSizeDomain::Slots),
257 _ => None,
258 }
259 }
260}
261
262const WINDOW_KIND_NAMES: [&str; 4] = ["tumbling", "sliding", "session", "rolling"];
263
264const WINDOW_KIND_SETS: [&[&str]; 16] = [
265 &[],
266 &["tumbling"],
267 &["sliding"],
268 &["tumbling", "sliding"],
269 &["session"],
270 &["tumbling", "session"],
271 &["sliding", "session"],
272 &["tumbling", "sliding", "session"],
273 &["rolling"],
274 &["tumbling", "rolling"],
275 &["sliding", "rolling"],
276 &["tumbling", "sliding", "rolling"],
277 &["session", "rolling"],
278 &["tumbling", "session", "rolling"],
279 &["sliding", "session", "rolling"],
280 &["tumbling", "sliding", "session", "rolling"],
281];
282
283impl WindowRequirements {
284 pub fn kinds_bitmask(&self) -> Option<u32> {
285 self.kinds.iter().try_fold(0u32, |mask, kind| {
286 WINDOW_KIND_NAMES.iter().position(|name| name == kind).map(|bit| mask | 1 << bit)
287 })
288 }
289
290 pub fn kinds_from_bitmask(mask: u32) -> Option<&'static [&'static str]> {
291 WINDOW_KIND_SETS.get(usize::try_from(mask).ok()?).copied()
292 }
293
294 pub fn check(&self, with: &ApplyWith) -> ValueResult<()> {
295 let kind = match (&with.window, self.takes_window) {
296 (Some(kind), true) => kind,
297 (Some(kind), false) => {
298 return Err(CoreError::OperatorWithWindowNotSupported {
299 kind: kind.name(),
300 }
301 .into());
302 }
303 (None, true) => return Err(CoreError::OperatorWithWindowMissing.into()),
304 (None, false) => return Ok(()),
305 };
306 if !self.kinds.contains(&kind.name()) {
307 return Err(CoreError::OperatorWithWindowKindUnsupported {
308 kind: kind.name(),
309 supported: self.kinds.join(", "),
310 }
311 .into());
312 }
313 match (self.domain, kind.size()) {
314 (WindowSizeDomain::Time, Some(WindowSize::Count(count))) => {
315 return Err(CoreError::OperatorWithWindowSizeCount {
316 count: *count,
317 }
318 .into());
319 }
320 (WindowSizeDomain::Slots, Some(WindowSize::Duration(size))) => {
321 return Err(CoreError::OperatorWithWindowSizeDuration {
322 size: *size,
323 }
324 .into());
325 }
326 _ => {}
327 }
328 if self.needs_pane
329 && matches!(
330 kind,
331 WindowKind::Rolling {
332 pane: None,
333 ..
334 }
335 ) {
336 return Err(CoreError::OperatorWithPaneMissing.into());
337 }
338 with.check_session_window()
339 }
340}
341
342#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
343pub enum TimeSource {
344 None,
345 Event {
346 ts: String,
347 },
348 Processing,
349}
350
351impl TimeSource {
352 pub fn domain(&self) -> TimeDomain {
353 match self {
354 TimeSource::None => TimeDomain::None,
355 TimeSource::Event {
356 ..
357 } => TimeDomain::Event,
358 TimeSource::Processing => TimeDomain::Processing,
359 }
360 }
361
362 pub fn ts(&self) -> Option<&str> {
363 match self {
364 TimeSource::Event {
365 ts,
366 } => Some(ts.as_str()),
367 TimeSource::None | TimeSource::Processing => None,
368 }
369 }
370}
371
372#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
373pub enum WindowKind {
374 Tumbling {
375 size: WindowSize,
376 },
377
378 Sliding {
379 size: WindowSize,
380 slide: WindowSize,
381 },
382
383 Rolling {
384 size: WindowSize,
385 #[serde(default)]
386 lag: Option<Duration>,
387 #[serde(default)]
388 pane: Option<Duration>,
389 },
390
391 Session {
392 gap: Duration,
393 },
394}
395
396impl WindowKind {
397 pub fn size(&self) -> Option<&WindowSize> {
398 match self {
399 WindowKind::Tumbling {
400 size,
401 ..
402 } => Some(size),
403 WindowKind::Sliding {
404 size,
405 ..
406 } => Some(size),
407 WindowKind::Rolling {
408 size,
409 ..
410 } => Some(size),
411 WindowKind::Session {
412 ..
413 } => None,
414 }
415 }
416
417 pub fn name(&self) -> &'static str {
418 match self {
419 WindowKind::Tumbling {
420 ..
421 } => "tumbling",
422 WindowKind::Sliding {
423 ..
424 } => "sliding",
425 WindowKind::Rolling {
426 ..
427 } => "rolling",
428 WindowKind::Session {
429 ..
430 } => "session",
431 }
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438
439 #[test]
440 fn every_operator_class_round_trips_through_its_byte_and_zero_is_refused() {
441 for class in [
443 OperatorClass::Managed,
444 OperatorClass::Unmanaged,
445 OperatorClass::Nostate,
446 OperatorClass::Windowed,
447 ] {
448 assert_eq!(OperatorClass::from_u8(class.to_u8()), Some(class));
449 }
450 assert_eq!(OperatorClass::from_u8(0), None);
451 assert_eq!(OperatorClass::from_u8(5), None);
452 }
453
454 #[test]
455 fn every_window_size_domain_round_trips_through_its_byte_and_zero_is_refused() {
456 for domain in [WindowSizeDomain::Time, WindowSizeDomain::Slots] {
458 assert_eq!(WindowSizeDomain::from_u8(domain.to_u8()), Some(domain));
459 }
460 assert_eq!(WindowSizeDomain::from_u8(0), None);
461 assert_eq!(WindowSizeDomain::from_u8(3), None);
462 }
463
464 fn requirements(kinds: &'static [&'static str]) -> WindowRequirements {
465 WindowRequirements {
466 takes_window: true,
467 kinds,
468 domain: WindowSizeDomain::Time,
469 needs_pane: false,
470 }
471 }
472
473 #[test]
474 fn every_published_kind_list_survives_the_bitmask() {
475 for kinds in [
477 &["tumbling"][..],
478 &["rolling"],
479 &["tumbling", "sliding"],
480 &["tumbling", "sliding", "rolling"],
481 &["tumbling", "sliding", "session"],
482 &["tumbling", "sliding", "session", "rolling"],
483 &[],
484 ] {
485 let mask = requirements(kinds).kinds_bitmask().unwrap();
486 assert_eq!(WindowRequirements::kinds_from_bitmask(mask), Some(kinds));
487 }
488 }
489
490 #[test]
491 fn each_kind_owns_exactly_one_bit() {
492 let bits: Vec<u32> = [&["tumbling"][..], &["sliding"], &["session"], &["rolling"]]
494 .into_iter()
495 .map(|kinds| requirements(kinds).kinds_bitmask().unwrap())
496 .collect();
497 assert_eq!(bits, vec![1, 2, 4, 8]);
498 }
499
500 #[test]
501 fn an_unknown_kind_name_or_bit_is_refused() {
502 assert_eq!(requirements(&["hopping"]).kinds_bitmask(), None);
504 assert_eq!(requirements(&["tumbling", "hopping"]).kinds_bitmask(), None);
505 assert_eq!(WindowRequirements::kinds_from_bitmask(16), None);
506 assert_eq!(WindowRequirements::kinds_from_bitmask(u32::MAX), None);
507 }
508}