risingwave_frontend/optimizer/plan_node/
stream_over_window.rs1use std::collections::HashSet;
16
17use pretty_xmlish::XmlNode;
18use risingwave_common::util::sort_util::{ColumnOrder, OrderType};
19use risingwave_expr::window_function::{FrameBounds, can_forward_watermark_on_order_key};
20use risingwave_pb::stream_plan::stream_node::PbNodeBody;
21
22use super::generic::{DistillUnit, GenericPlanNode, PlanWindowFunction};
23use super::stream::prelude::*;
24use super::utils::{Distill, TableCatalogBuilder, watermark_pretty};
25use super::{
26 ExprRewritable, PlanBase, PlanTreeNodeUnary, StreamNode, StreamPlanRef as PlanRef, generic,
27};
28use crate::TableCatalog;
29use crate::optimizer::plan_node::expr_visitable::ExprVisitable;
30use crate::optimizer::property::MonotonicityMap;
31use crate::stream_fragmenter::BuildFragmentGraphState;
32
33#[derive(Debug, Clone, PartialEq, Eq, Hash)]
34pub struct StreamOverWindow {
35 pub base: PlanBase<Stream>,
36 core: generic::OverWindow<PlanRef>,
37}
38
39impl StreamOverWindow {
40 pub fn new(core: generic::OverWindow<PlanRef>) -> Result<Self> {
41 assert!(core.funcs_have_same_partition_and_order());
42 reject_upsert_input!(core.input);
43
44 let input = &core.input;
45 let watermark_columns = {
46 let mut cols = core.partition_key_indices();
51 if let Some(first_order_key) = core.order_key().first()
52 && can_forward_watermark_on_order_key(
53 core.window_functions().iter().map(|func| &func.frame),
54 first_order_key.order_type,
55 )
56 {
57 cols.push(first_order_key.column_index);
58 }
59 input.watermark_columns().retain_clone(&cols)
60 };
61
62 let base = PlanBase::new_stream_with_core(
63 &core,
64 input.distribution().clone(),
65 StreamKind::Retract, false,
67 watermark_columns,
68 MonotonicityMap::new(), );
70
71 Ok(StreamOverWindow { base, core })
72 }
73
74 fn infer_state_table(&self) -> TableCatalog {
75 let mut tbl_builder = TableCatalogBuilder::default();
76
77 let out_schema = self.core.schema();
78 for field in out_schema.fields() {
79 tbl_builder.add_column(field);
80 }
81
82 let mut order_cols = HashSet::new();
83 for idx in self.core.partition_key_indices() {
84 if order_cols.insert(idx) {
85 tbl_builder.add_order_column(idx, OrderType::ascending());
86 }
87 }
88 let read_prefix_len_hint = tbl_builder.get_current_pk_len();
89 for o in self.core.order_key() {
90 if order_cols.insert(o.column_index) {
91 tbl_builder.add_order_column(o.column_index, o.order_type);
92 }
93 }
94 for &idx in self.core.input.expect_stream_key() {
95 if order_cols.insert(idx) {
96 tbl_builder.add_order_column(idx, OrderType::ascending());
97 }
98 }
99
100 let in_dist_key = self.core.input.distribution().dist_column_indices();
101 tbl_builder.build(in_dist_key.to_vec(), read_prefix_len_hint)
102 }
103
104 fn state_cleaning_enabled(&self) -> bool {
115 let input = &self.core.input;
116 let Some(first_order_key) = self.core.order_key().first() else {
117 return false;
118 };
119 input.append_only()
120 && self.core.window_functions().iter().all(|func| {
121 matches!(&func.frame.bounds, FrameBounds::Rows(bounds)
122 if !bounds.start.is_unbounded_preceding() && !bounds.end.is_unbounded_following())
123 })
124 && first_order_key.order_type.nulls_are_largest()
125 && input.watermark_columns().contains(first_order_key.column_index)
126 && !self
129 .core
130 .partition_key_indices()
131 .contains(&first_order_key.column_index)
132 }
133}
134
135impl Distill for StreamOverWindow {
136 fn distill<'a>(&self) -> XmlNode<'a> {
137 let mut node = self.core.distill_with_name("StreamOverWindow");
138 if let Some(ow) = watermark_pretty(self.base.watermark_columns(), self.schema()) {
139 node.fields.push(("output_watermarks".into(), ow));
140 }
141 node
142 }
143}
144
145impl PlanTreeNodeUnary<Stream> for StreamOverWindow {
146 fn input(&self) -> PlanRef {
147 self.core.input.clone()
148 }
149
150 fn clone_with_input(&self, input: PlanRef) -> Self {
151 let mut core = self.core.clone();
152 core.input = input;
153 Self::new(core).unwrap()
154 }
155}
156impl_plan_tree_node_for_unary! { Stream, StreamOverWindow }
157
158impl StreamNode for StreamOverWindow {
159 fn to_stream_prost_body(&self, state: &mut BuildFragmentGraphState) -> PbNodeBody {
160 use risingwave_pb::stream_plan::*;
161
162 let calls = self
163 .core
164 .window_functions()
165 .iter()
166 .map(PlanWindowFunction::to_protobuf)
167 .collect();
168 let partition_by = self
169 .core
170 .partition_key_indices()
171 .into_iter()
172 .map(|idx| idx as _)
173 .collect();
174 let order_by = self
175 .core
176 .order_key()
177 .iter()
178 .copied()
179 .map(ColumnOrder::to_protobuf)
180 .collect();
181 let state_table = self
182 .infer_state_table()
183 .with_id(state.gen_table_id_wrapped())
184 .to_internal_table_prost();
185
186 PbNodeBody::OverWindow(Box::new(OverWindowNode {
187 calls,
188 partition_by,
189 order_by,
190 state_table: Some(state_table),
191
192 #[expect(deprecated)]
194 cache_policy: PbOverWindowCachePolicy::Unspecified as _,
195
196 enable_state_cleaning: self.state_cleaning_enabled(),
197 }))
198 }
199}
200
201impl ExprRewritable<Stream> for StreamOverWindow {}
202
203impl ExprVisitable for StreamOverWindow {}