risingwave_frontend/optimizer/plan_node/
stream_gap_fill.rs1use risingwave_common::util::sort_util::OrderType;
16use risingwave_pb::stream_plan::stream_node::NodeBody;
17
18use super::generic::GenericPlanNode;
19use super::utils::TableCatalogBuilder;
20use super::{
21 ExprRewritable, ExprVisitable, PlanBase, PlanRef, PlanTreeNodeUnary, Stream, TryToStreamPb,
22 generic,
23};
24use crate::binder::BoundFillStrategy;
25use crate::expr::{Expr, ExprImpl, ExprRewriter, ExprVisitor, InputRef};
26use crate::optimizer::plan_node::stream::StreamPlanNodeMetadata;
27use crate::optimizer::plan_node::utils::impl_distill_by_unit;
28use crate::optimizer::property::{Distribution, MonotonicityMap, WatermarkColumns};
29use crate::scheduler::SchedulerResult;
30use crate::stream_fragmenter::BuildFragmentGraphState;
31
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
35pub struct StreamGapFill {
36 pub base: PlanBase<super::Stream>,
37 core: generic::GapFill<PlanRef<Stream>>,
38}
39
40impl StreamGapFill {
41 pub fn new(core: generic::GapFill<PlanRef<Stream>>) -> Self {
42 let input = &core.input;
43 let partition_indices = core.partition_key_indices();
44 let distinct_partition_key_count = partition_indices
45 .iter()
46 .copied()
47 .collect::<std::collections::HashSet<_>>()
48 .len();
49 assert_eq!(
50 partition_indices.len(),
51 distinct_partition_key_count,
52 "stream gap fill expects canonicalized partition_by columns",
53 );
54 assert!(
55 !core
56 .pointer_key_indices()
57 .expect("stream gap fill input should have stream key")
58 .is_empty(),
59 "stream gap fill pointer key should not be empty",
60 );
61
62 let dist = if core.partition_by_cols.is_empty() {
63 Distribution::Single
64 } else {
65 Distribution::HashShard(partition_indices)
66 };
67
68 let base = PlanBase::new_stream_with_core(
71 &core,
72 dist,
73 input.stream_kind(),
74 false, WatermarkColumns::new(),
76 MonotonicityMap::new(),
77 );
78 Self { base, core }
79 }
80
81 pub fn time_col(&self) -> &InputRef {
82 &self.core.time_col
83 }
84
85 pub fn interval(&self) -> &ExprImpl {
86 &self.core.interval
87 }
88
89 pub fn fill_strategies(&self) -> &[BoundFillStrategy] {
90 &self.core.fill_strategies
91 }
92
93 fn pointer_key_indices(&self) -> Vec<usize> {
94 self.core
95 .pointer_key_indices()
96 .expect("stream gap fill input should have stream key")
97 }
98
99 fn infer_state_table(&self) -> crate::TableCatalog {
100 let mut tbl_builder = TableCatalogBuilder::default();
101
102 let out_schema = self.core.schema();
103 for field in out_schema.fields() {
104 tbl_builder.add_column(field);
105 }
106
107 let state_key_indices = self
108 .core
109 .stream_key_indices()
110 .expect("stream gap fill input should have stream key");
111
112 for key_idx in state_key_indices {
114 tbl_builder.add_order_column(key_idx, OrderType::ascending());
115 }
116
117 let partition_key_indices = self.core.partition_key_indices();
118 let read_prefix_len_hint = partition_key_indices.len();
119 tbl_builder.build(partition_key_indices, read_prefix_len_hint)
120 }
121}
122
123impl PlanTreeNodeUnary<Stream> for StreamGapFill {
124 fn input(&self) -> PlanRef<Stream> {
125 self.core.input.clone()
126 }
127
128 fn clone_with_input(&self, input: PlanRef<Stream>) -> Self {
129 let mut core = self.core.clone();
130 core.input = input;
131 Self::new(core)
132 }
133}
134
135impl_plan_tree_node_for_unary! { Stream, StreamGapFill }
136impl_distill_by_unit!(StreamGapFill, core, "StreamGapFill");
137
138impl TryToStreamPb for StreamGapFill {
139 fn try_to_stream_prost_body(
140 &self,
141 state: &mut BuildFragmentGraphState,
142 ) -> SchedulerResult<NodeBody> {
143 use risingwave_pb::stream_plan::*;
144
145 let fill_strategies: Vec<String> = self
146 .fill_strategies()
147 .iter()
148 .map(|strategy| match strategy.strategy {
149 crate::binder::FillStrategy::Locf => "locf".to_owned(),
150 crate::binder::FillStrategy::Interpolate => "interpolate".to_owned(),
151 crate::binder::FillStrategy::Null => "null".to_owned(),
152 })
153 .collect();
154
155 let state_table = self
156 .infer_state_table()
157 .with_id(state.gen_table_id_wrapped())
158 .to_internal_table_prost();
159
160 Ok(NodeBody::GapFill(Box::new(GapFillNode {
161 pointer_key_indices: self
162 .pointer_key_indices()
163 .into_iter()
164 .map(|idx| idx as u32)
165 .collect(),
166 time_column_index: self.time_col().index() as u32,
167 interval: Some(self.interval().to_expr_proto_checked_pure(
168 self.stream_kind().is_retract(),
169 "gap filling interval",
170 )?),
171 fill_columns: self
172 .fill_strategies()
173 .iter()
174 .map(|strategy| strategy.target_col.index() as u32)
175 .collect(),
176 fill_strategies,
177 state_table: Some(state_table),
178 partition_by_indices: self
179 .core
180 .partition_by_cols
181 .iter()
182 .map(|c| c.index() as u32)
183 .collect(),
184 })))
185 }
186}
187
188impl ExprRewritable<Stream> for StreamGapFill {
189 fn has_rewritable_expr(&self) -> bool {
190 true
191 }
192
193 fn rewrite_exprs(&self, r: &mut dyn ExprRewriter) -> PlanRef<Stream> {
194 let mut core = self.core.clone();
195 core.rewrite_exprs(r);
196 Self {
197 base: self.base.clone_with_new_plan_id(),
198 core,
199 }
200 .into()
201 }
202}
203
204impl ExprVisitable for StreamGapFill {
205 fn visit_exprs(&self, v: &mut dyn ExprVisitor) {
206 self.core.visit_exprs(v)
207 }
208}