1use std::fmt::Display;
16
17use FrameBound::{CurrentRow, Following, Preceding, UnboundedFollowing, UnboundedPreceding};
18use enum_as_inner::EnumAsInner;
19use parse_display::Display;
20use risingwave_common::types::DataType;
21use risingwave_common::util::sort_util::OrderType;
22use risingwave_common::{bail, must_match};
23use risingwave_pb::expr::window_frame::{PbBounds, PbExclusion};
24use risingwave_pb::expr::{PbWindowFrame, PbWindowFunction};
25
26use super::{
27 RangeFrameBounds, RowsFrameBound, RowsFrameBounds, SessionFrameBounds, WindowFuncKind,
28};
29use crate::Result;
30use crate::aggregate::AggArgs;
31
32#[derive(Debug, Clone)]
33pub struct WindowFuncCall {
34 pub kind: WindowFuncKind,
35 pub return_type: DataType,
36 pub args: AggArgs,
37 pub ignore_nulls: bool,
38 pub frame: Frame,
39}
40
41impl WindowFuncCall {
42 pub fn from_protobuf(call: &PbWindowFunction) -> Result<Self> {
43 let call = WindowFuncCall {
44 kind: WindowFuncKind::from_protobuf(call.get_type()?)?,
45 return_type: DataType::from(call.get_return_type()?),
46 args: AggArgs::from_protobuf(call.get_args())?,
47 ignore_nulls: call.get_ignore_nulls(),
48 frame: Frame::from_protobuf(call.get_frame()?)?,
49 };
50 Ok(call)
51 }
52}
53
54#[derive(Debug, Clone, Eq, PartialEq, Hash)]
55pub struct Frame {
56 pub bounds: FrameBounds,
57 pub exclusion: FrameExclusion,
58}
59
60impl Display for Frame {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 write!(f, "{}", self.bounds)?;
63 if self.exclusion != FrameExclusion::default() {
64 write!(f, " {}", self.exclusion)?;
65 }
66 Ok(())
67 }
68}
69
70impl Frame {
71 pub fn rows(start: RowsFrameBound, end: RowsFrameBound) -> Self {
72 Self {
73 bounds: FrameBounds::Rows(RowsFrameBounds { start, end }),
74 exclusion: FrameExclusion::default(),
75 }
76 }
77
78 pub fn rows_with_exclusion(
79 start: RowsFrameBound,
80 end: RowsFrameBound,
81 exclusion: FrameExclusion,
82 ) -> Self {
83 Self {
84 bounds: FrameBounds::Rows(RowsFrameBounds { start, end }),
85 exclusion,
86 }
87 }
88}
89
90impl Frame {
91 pub fn from_protobuf(frame: &PbWindowFrame) -> Result<Self> {
92 use risingwave_pb::expr::window_frame::PbType;
93 let bounds = match frame.get_type()? {
94 PbType::Unspecified => bail!("unspecified type of `WindowFrame`"),
95 #[expect(deprecated)]
96 PbType::RowsLegacy => {
97 #[expect(deprecated)]
98 {
99 let start = FrameBound::<usize>::from_protobuf_legacy(frame.get_start()?)?;
100 let end = FrameBound::<usize>::from_protobuf_legacy(frame.get_end()?)?;
101 FrameBounds::Rows(RowsFrameBounds { start, end })
102 }
103 }
104 PbType::Rows => {
105 let bounds = must_match!(frame.get_bounds()?, PbBounds::Rows(bounds) => bounds);
106 FrameBounds::Rows(RowsFrameBounds::from_protobuf(bounds)?)
107 }
108 PbType::Range => {
109 let bounds = must_match!(frame.get_bounds()?, PbBounds::Range(bounds) => bounds);
110 FrameBounds::Range(RangeFrameBounds::from_protobuf(bounds)?)
111 }
112 PbType::Session => {
113 let bounds = must_match!(frame.get_bounds()?, PbBounds::Session(bounds) => bounds);
114 FrameBounds::Session(SessionFrameBounds::from_protobuf(bounds)?)
115 }
116 };
117 let exclusion = FrameExclusion::from_protobuf(frame.get_exclusion()?)?;
118 Ok(Self { bounds, exclusion })
119 }
120
121 pub fn to_protobuf(&self) -> PbWindowFrame {
122 use risingwave_pb::expr::window_frame::PbType;
123 let exclusion = self.exclusion.to_protobuf() as _;
124 #[expect(deprecated)] match &self.bounds {
126 FrameBounds::Rows(bounds) => PbWindowFrame {
127 r#type: PbType::Rows as _,
128 start: None, end: None, exclusion,
131 bounds: Some(PbBounds::Rows(bounds.to_protobuf())),
132 },
133 FrameBounds::Range(bounds) => PbWindowFrame {
134 r#type: PbType::Range as _,
135 start: None, end: None, exclusion,
138 bounds: Some(PbBounds::Range(bounds.to_protobuf())),
139 },
140 FrameBounds::Session(bounds) => PbWindowFrame {
141 r#type: PbType::Session as _,
142 start: None, end: None, exclusion,
145 bounds: Some(PbBounds::Session(bounds.to_protobuf())),
146 },
147 }
148 }
149}
150
151#[derive(Display, Debug, Clone, Eq, PartialEq, Hash, EnumAsInner)]
152#[display("{0}")]
153pub enum FrameBounds {
154 Rows(RowsFrameBounds),
155 Range(RangeFrameBounds),
157 Session(SessionFrameBounds),
158}
159
160impl FrameBounds {
161 pub fn validate(&self) -> Result<()> {
162 match self {
163 Self::Rows(bounds) => bounds.validate(),
164 Self::Range(bounds) => bounds.validate(),
165 Self::Session(bounds) => bounds.validate(),
166 }
167 }
168
169 pub fn start_is_unbounded(&self) -> bool {
170 match self {
171 Self::Rows(RowsFrameBounds { start, .. }) => start.is_unbounded_preceding(),
172 Self::Range(RangeFrameBounds { start, .. }) => start.is_unbounded_preceding(),
173 Self::Session(_) => false,
174 }
175 }
176
177 pub fn end_is_unbounded(&self) -> bool {
178 match self {
179 Self::Rows(RowsFrameBounds { end, .. }) => end.is_unbounded_following(),
180 Self::Range(RangeFrameBounds { end, .. }) => end.is_unbounded_following(),
181 Self::Session(_) => false,
182 }
183 }
184
185 pub fn is_unbounded(&self) -> bool {
186 self.start_is_unbounded() || self.end_is_unbounded()
187 }
188
189 pub fn may_include_preceding_rows(&self) -> bool {
191 match self {
192 Self::Rows(RowsFrameBounds { start, .. }) => {
193 start.is_unbounded_preceding() || start.is_preceding()
194 }
195 Self::Range(RangeFrameBounds { start, .. }) => {
196 start.is_unbounded_preceding() || start.is_preceding()
197 }
198 Self::Session(_) => true,
199 }
200 }
201
202 pub fn may_include_following_rows(&self) -> bool {
204 match self {
205 Self::Rows(RowsFrameBounds { end, .. }) => {
206 end.is_unbounded_following() || end.is_following()
207 }
208 Self::Range(RangeFrameBounds { end, .. }) => {
209 end.is_unbounded_following() || end.is_following()
210 }
211 Self::Session(_) => true,
212 }
213 }
214}
215
216pub fn can_forward_watermark_on_order_key<'a>(
232 frames: impl IntoIterator<Item = &'a Frame>,
233 first_order_type: OrderType,
234) -> bool {
235 if !first_order_type.nulls_are_largest() {
236 return false;
237 }
238 let mut frames = frames.into_iter();
239 if first_order_type.is_ascending() {
240 frames.all(|frame| !frame.bounds.may_include_following_rows())
241 } else {
242 frames.all(|frame| !frame.bounds.may_include_preceding_rows())
243 }
244}
245
246pub trait FrameBoundsImpl {
247 fn validate(&self) -> Result<()>;
248}
249
250#[derive(Display, Debug, Clone, Eq, PartialEq, Hash, EnumAsInner)]
251#[display(style = "TITLE CASE")]
252pub enum FrameBound<T> {
253 UnboundedPreceding,
254 #[display("{0} PRECEDING")]
255 Preceding(T),
256 CurrentRow,
257 #[display("{0} FOLLOWING")]
258 Following(T),
259 UnboundedFollowing,
260}
261
262impl<T> FrameBound<T> {
263 fn offset_value(&self) -> Option<&T> {
264 match self {
265 UnboundedPreceding | UnboundedFollowing | CurrentRow => None,
266 Preceding(offset) | Following(offset) => Some(offset),
267 }
268 }
269
270 pub(super) fn validate_bounds(
271 start: &Self,
272 end: &Self,
273 offset_checker: impl Fn(&T) -> Result<()>,
274 ) -> Result<()> {
275 match (start, end) {
276 (_, UnboundedPreceding) => bail!("frame end cannot be UNBOUNDED PRECEDING"),
277 (UnboundedFollowing, _) => {
278 bail!("frame start cannot be UNBOUNDED FOLLOWING")
279 }
280 (Following(_), CurrentRow) | (Following(_), Preceding(_)) => {
281 bail!("frame starting from following row cannot have preceding rows")
282 }
283 (CurrentRow, Preceding(_)) => {
284 bail!("frame starting from current row cannot have preceding rows")
285 }
286 _ => {}
287 }
288
289 for bound in [start, end] {
290 if let Some(offset) = bound.offset_value() {
291 offset_checker(offset)?;
292 }
293 }
294
295 Ok(())
296 }
297
298 pub fn map<U>(self, f: impl Fn(T) -> U) -> FrameBound<U> {
299 match self {
300 UnboundedPreceding => UnboundedPreceding,
301 Preceding(offset) => Preceding(f(offset)),
302 CurrentRow => CurrentRow,
303 Following(offset) => Following(f(offset)),
304 UnboundedFollowing => UnboundedFollowing,
305 }
306 }
307}
308
309impl<T> FrameBound<T>
310where
311 T: Copy,
312{
313 pub(super) fn reverse(self) -> FrameBound<T> {
314 match self {
315 UnboundedPreceding => UnboundedFollowing,
316 Preceding(offset) => Following(offset),
317 CurrentRow => CurrentRow,
318 Following(offset) => Preceding(offset),
319 UnboundedFollowing => UnboundedPreceding,
320 }
321 }
322}
323
324#[derive(Display, Debug, Copy, Clone, Eq, PartialEq, Hash, Default, EnumAsInner)]
325#[display("EXCLUDE {}", style = "TITLE CASE")]
326pub enum FrameExclusion {
327 CurrentRow,
328 #[default]
331 NoOthers,
332}
333
334impl FrameExclusion {
335 fn from_protobuf(exclusion: PbExclusion) -> Result<Self> {
336 let excl = match exclusion {
337 PbExclusion::Unspecified => bail!("unspecified type of `FrameExclusion`"),
338 PbExclusion::CurrentRow => Self::CurrentRow,
339 PbExclusion::NoOthers => Self::NoOthers,
340 };
341 Ok(excl)
342 }
343
344 fn to_protobuf(self) -> PbExclusion {
345 match self {
346 Self::CurrentRow => PbExclusion::CurrentRow,
347 Self::NoOthers => PbExclusion::NoOthers,
348 }
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use risingwave_common::types::ScalarImpl;
355
356 use super::*;
357 use crate::window_function::{RangeFrameBound, RangeFrameOffset, SessionFrameGap};
358
359 fn rows_frame(start: RowsFrameBound, end: RowsFrameBound) -> Frame {
360 Frame::rows(start, end)
361 }
362
363 fn range_frame(start: FrameBound<i64>, end: FrameBound<i64>, order_type: OrderType) -> Frame {
364 let bound = |bound: FrameBound<i64>| -> RangeFrameBound {
366 bound.map(|offset| RangeFrameOffset::new(ScalarImpl::Int64(offset)))
367 };
368 Frame {
369 bounds: FrameBounds::Range(RangeFrameBounds {
370 order_data_type: DataType::Int64,
371 order_type,
372 offset_data_type: DataType::Int64,
373 start: bound(start),
374 end: bound(end),
375 }),
376 exclusion: FrameExclusion::default(),
377 }
378 }
379
380 fn session_frame(order_type: OrderType) -> Frame {
381 Frame {
382 bounds: FrameBounds::Session(SessionFrameBounds {
383 order_data_type: DataType::Int64,
384 order_type,
385 gap_data_type: DataType::Int64,
386 gap: SessionFrameGap::new(ScalarImpl::Int64(10)),
387 }),
388 exclusion: FrameExclusion::default(),
389 }
390 }
391
392 #[test]
393 fn test_can_forward_watermark_on_order_key() {
394 let asc = OrderType::ascending(); let desc = OrderType::descending(); let can_forward =
397 |frames: &[Frame], order_type| can_forward_watermark_on_order_key(frames, order_type);
398
399 let preceding_only = [
400 rows_frame(UnboundedPreceding, CurrentRow), rows_frame(Preceding(1), Preceding(1)), rows_frame(Preceding(2), CurrentRow),
403 ];
404 let following_only = [
405 rows_frame(CurrentRow, UnboundedFollowing),
406 rows_frame(Following(1), Following(1)), rows_frame(CurrentRow, Following(2)),
408 ];
409 let current_row_only = [rows_frame(CurrentRow, CurrentRow)];
410 let both_sides = [
411 rows_frame(Preceding(1), Following(1)),
412 rows_frame(UnboundedPreceding, UnboundedFollowing),
413 ];
414
415 assert!(can_forward(&preceding_only, asc));
416 assert!(!can_forward(&preceding_only, desc));
417 assert!(!can_forward(&following_only, asc));
418 assert!(can_forward(&following_only, desc));
419 assert!(can_forward(¤t_row_only, asc));
420 assert!(can_forward(¤t_row_only, desc));
421 assert!(!can_forward(&both_sides, asc));
422 assert!(!can_forward(&both_sides, desc));
423
424 let mixed = [
426 rows_frame(Preceding(1), Preceding(1)),
427 rows_frame(Following(1), Following(1)),
428 ];
429 assert!(!can_forward(&mixed, asc));
430 assert!(!can_forward(&mixed, desc));
431
432 for order_type in [asc, desc] {
434 let preceding_only = [range_frame(Preceding(10), CurrentRow, order_type)];
435 let following_only = [range_frame(CurrentRow, Following(10), order_type)];
436 let both_sides = [range_frame(Preceding(10), Following(10), order_type)];
437 assert_eq!(
438 can_forward(&preceding_only, order_type),
439 order_type.is_ascending()
440 );
441 assert_eq!(
442 can_forward(&following_only, order_type),
443 order_type.is_descending()
444 );
445 assert!(!can_forward(&both_sides, order_type));
446 }
447
448 for order_type in [asc, desc] {
450 assert!(!can_forward(&[session_frame(order_type)], order_type));
451 }
452
453 assert!(!can_forward(
455 &preceding_only,
456 OrderType::ascending_nulls_first()
457 ));
458 assert!(!can_forward(
459 &following_only,
460 OrderType::descending_nulls_last()
461 ));
462 assert!(can_forward(
463 &preceding_only,
464 OrderType::ascending_nulls_last()
465 ));
466 assert!(can_forward(
467 &following_only,
468 OrderType::descending_nulls_first()
469 ));
470 }
471}