risingwave_frontend/optimizer/plan_node/
logical_changelog.rs1use itertools::Itertools;
16
17use super::expr_visitable::ExprVisitable;
18use super::generic::{_CHANGELOG_ROW_ID, CHANGELOG_OP, GenericPlanRef};
19use super::utils::impl_distill_by_unit;
20use super::{
21 BatchPlanRef, ColPrunable, ColumnPruningContext, ExprRewritable, Logical,
22 LogicalPlanRef as PlanRef, LogicalProject, PlanBase, PlanTreeNodeUnary, PredicatePushdown,
23 RewriteStreamContext, StreamChangeLog, StreamPlanRef, ToBatch, ToStream, ToStreamContext,
24 gen_filter_and_pushdown, generic,
25};
26use crate::error::ErrorCode::BindError;
27use crate::error::Result;
28use crate::optimizer::plan_node::generic::PhysicalPlanRef;
29use crate::optimizer::property::{Distribution, RequiredDist};
30use crate::utils::{ColIndexMapping, Condition};
31
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
33pub struct LogicalChangeLog {
34 pub base: PlanBase<Logical>,
35 core: generic::ChangeLog<PlanRef>,
36}
37
38impl LogicalChangeLog {
39 pub fn create(
40 input: PlanRef,
41 key_indices: Option<Vec<usize>>,
42 distribution_key_indices: Option<Vec<usize>>,
43 ) -> PlanRef {
44 Self::new(input, key_indices, distribution_key_indices, true, true).into()
45 }
46
47 pub fn new(
48 input: PlanRef,
49 key_indices: Option<Vec<usize>>,
50 distribution_key_indices: Option<Vec<usize>>,
51 need_op: bool,
52 need_changelog_row_id: bool,
53 ) -> Self {
54 let core = generic::ChangeLog::new(
55 input,
56 key_indices,
57 distribution_key_indices,
58 need_op,
59 need_changelog_row_id,
60 );
61 Self::with_core(core)
62 }
63
64 pub fn with_core(core: generic::ChangeLog<PlanRef>) -> Self {
65 let base = PlanBase::new_logical_with_core(&core);
66 LogicalChangeLog { base, core }
67 }
68}
69
70impl PlanTreeNodeUnary<Logical> for LogicalChangeLog {
71 fn input(&self) -> PlanRef {
72 self.core.input.clone()
73 }
74
75 fn clone_with_input(&self, input: PlanRef) -> Self {
76 let core = self.core.clone_with_input(input);
77
78 Self::with_core(core)
79 }
80
81 fn rewrite_with_input(
82 &self,
83 input: PlanRef,
84 input_col_change: ColIndexMapping,
85 ) -> (Self, ColIndexMapping) {
86 let key_indices = self
87 .core
88 .key_indices
89 .as_ref()
90 .map(|key| input_col_change.map_all(key));
91 let distribution_key_indices = self
92 .core
93 .distribution_key_indices
94 .as_ref()
95 .map(|key| input_col_change.map_all(key));
96 let changelog = Self::new(
97 input,
98 key_indices,
99 distribution_key_indices,
100 self.core.need_op,
101 true,
102 );
103
104 let out_col_change = if self.core.need_op {
105 let (mut output_vec, len) = input_col_change.into_parts();
106 output_vec.push(Some(len));
107 ColIndexMapping::new(output_vec, len + 1)
108 } else {
109 input_col_change
110 };
111
112 let (mut output_vec, len) = out_col_change.into_parts();
113 let out_col_change = if self.core.need_changelog_row_id {
114 output_vec.push(Some(len));
115 ColIndexMapping::new(output_vec, len + 1)
116 } else {
117 ColIndexMapping::new(output_vec, len + 1)
118 };
119
120 (changelog, out_col_change)
121 }
122}
123
124impl_plan_tree_node_for_unary! { Logical, LogicalChangeLog}
125impl_distill_by_unit!(LogicalChangeLog, core, "LogicalChangeLog");
126
127impl ExprRewritable<Logical> for LogicalChangeLog {}
128
129impl ExprVisitable for LogicalChangeLog {}
130
131impl PredicatePushdown for LogicalChangeLog {
132 fn predicate_pushdown(
133 &self,
134 predicate: Condition,
135 ctx: &mut super::PredicatePushdownContext,
136 ) -> PlanRef {
137 gen_filter_and_pushdown(self, predicate, Condition::true_cond(), ctx)
138 }
139}
140
141impl ColPrunable for LogicalChangeLog {
142 fn prune_col(&self, required_cols: &[usize], ctx: &mut ColumnPruningContext) -> PlanRef {
143 let fields = self.schema().fields();
144 let mut need_op = false;
145 let mut need_changelog_row_id = false;
146 let mut input_required_cols = required_cols
149 .iter()
150 .filter_map(|a| {
151 if let Some(f) = fields.get(*a) {
152 if f.name == CHANGELOG_OP {
153 need_op = true;
154 None
155 } else if f.name == _CHANGELOG_ROW_ID {
156 need_changelog_row_id = true;
157 None
158 } else {
159 Some(*a)
160 }
161 } else {
162 Some(*a)
163 }
164 })
165 .collect_vec();
166
167 if let Some(stream_key) = self.input().stream_key() {
171 for &key in stream_key {
172 if !input_required_cols.contains(&key) {
173 input_required_cols.push(key);
174 }
175 }
176 }
177
178 if let Some(key) = &self.core.key_indices {
180 for &index in key {
181 if !input_required_cols.contains(&index) {
182 input_required_cols.push(index);
183 }
184 }
185 }
186
187 if let Some(key) = &self.core.distribution_key_indices {
189 for &index in key {
190 if !input_required_cols.contains(&index) {
191 input_required_cols.push(index);
192 }
193 }
194 }
195
196 let new_input = self.input().prune_col(&input_required_cols, ctx);
197 let input_mapping = ColIndexMapping::with_remaining_columns(
198 &input_required_cols,
199 self.input().schema().len(),
200 );
201 let key_indices = self
202 .core
203 .key_indices
204 .as_ref()
205 .map(|key| input_mapping.map_all(key));
206
207 let distribution_key_indices = self
208 .core
209 .distribution_key_indices
210 .as_ref()
211 .map(|key| input_mapping.map_all(key));
212
213 let changelog: PlanRef = Self::new(
215 new_input,
216 key_indices,
217 distribution_key_indices,
218 need_op,
219 need_changelog_row_id,
220 )
221 .into();
222
223 let (mut output_mapping, new_output_len) = input_mapping.into_parts();
225
226 if self.core.need_op {
227 output_mapping.push(need_op.then_some(new_output_len));
228 }
229
230 if self.core.need_changelog_row_id {
231 output_mapping
232 .push(need_changelog_row_id.then_some(new_output_len + usize::from(need_op)));
233 }
234
235 let output_len = changelog.schema().len();
236 let output_mapping = ColIndexMapping::new(output_mapping, output_len);
237 let output_required_cols = output_mapping.map_all(required_cols);
238
239 if output_required_cols.iter().copied().eq(0..output_len) {
240 changelog
241 } else {
242 let output_mapping =
243 ColIndexMapping::with_remaining_columns(&output_required_cols, output_len);
244
245 LogicalProject::with_mapping(changelog, output_mapping).into()
246 }
247 }
248}
249
250impl ToBatch for LogicalChangeLog {
251 fn to_batch(&self) -> Result<BatchPlanRef> {
252 Err(BindError("With changelog cte only support with create mv/sink".to_owned()).into())
253 }
254}
255
256impl ToStream for LogicalChangeLog {
257 fn to_stream(&self, ctx: &mut ToStreamContext) -> Result<StreamPlanRef> {
258 let input = self.input().to_stream(ctx)?;
259
260 let input = if let Some(key) = &self.core.distribution_key_indices {
261 RequiredDist::hash_shard(key).streaming_enforce_if_not_satisfies(input)?
262 } else if let Some(key) = &self.core.key_indices {
263 RequiredDist::hash_shard(key).streaming_enforce_if_not_satisfies(input)?
264 } else if matches!(input.distribution(), Distribution::SomeShard) {
265 RequiredDist::hash_shard(input.expect_stream_key())
266 .streaming_enforce_if_not_satisfies(input)?
267 } else {
268 input
269 };
270
271 let row_id_index = self.schema().fields().len() - 1;
272 let (distribution_keys, dist) = match input.distribution() {
276 Distribution::HashShard(distribution_keys)
277 | Distribution::UpstreamHashShard(distribution_keys, _) => (
278 distribution_keys.clone(),
279 Distribution::HashShard(vec![row_id_index]),
280 ),
281 Distribution::Single => (vec![], Distribution::Single),
282 dist => {
283 return Err(BindError(format!(
284 "ChangeLog requires input to be hash distributed, single, but got {:?}",
285 dist
286 ))
287 .into());
288 }
289 };
290 let core = self.core.clone_with_input(input);
291 let plan = StreamChangeLog::new_with_dist(
292 core,
293 dist,
294 distribution_keys.into_iter().map(|k| k as u32).collect(),
295 )
296 .into();
297
298 Ok(plan)
299 }
300
301 fn logical_rewrite_for_stream(
302 &self,
303 ctx: &mut RewriteStreamContext,
304 ) -> Result<(PlanRef, ColIndexMapping)> {
305 let (input, input_col_change) = self.input().logical_rewrite_for_stream(ctx)?;
306 let (changelog, out_col_change) = self.rewrite_with_input(input, input_col_change);
307 Ok((changelog.into(), out_col_change))
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use std::rc::Rc;
314
315 use risingwave_common::catalog::{CdcTableDesc, ColumnDesc, ColumnId, TableId};
316 use risingwave_common::id::SourceId;
317 use risingwave_common::types::DataType;
318 use risingwave_common::util::sort_util::{ColumnOrder, OrderType};
319 use risingwave_connector::source::cdc::CdcScanOptions;
320
321 use super::*;
322 use crate::optimizer::optimizer_context::OptimizerContext;
323 use crate::optimizer::plan_node::{BackfillType, LogicalCdcScan, PlanTreeNodeUnary};
324
325 #[test]
326 fn test_changelog_on_cdc_scan_enforces_hash_distribution() {
327 let desc = CdcTableDesc {
328 table_id: TableId::new(1),
329 source_id: SourceId::new(2),
330 external_table_name: "mydb.orders".to_owned(),
331 pk: vec![ColumnOrder::new(0, OrderType::ascending())],
332 pk_comparisons: vec![risingwave_common::catalog::CdcKeyComparison::Native],
333 columns: vec![
334 ColumnDesc::named("id", ColumnId::new(1), DataType::Int32),
335 ColumnDesc::named("payload", ColumnId::new(2), DataType::Varchar),
336 ],
337 stream_key: vec![0],
338 ..Default::default()
339 };
340 let scan = LogicalCdcScan::create(
341 "orders".to_owned(),
342 Rc::new(desc),
343 OptimizerContext::mock(),
344 CdcScanOptions {
345 disable_backfill: true,
346 ..Default::default()
347 },
348 );
349 let changelog = LogicalChangeLog::create(scan.clone().into(), None, None);
350 let stream = changelog
351 .to_stream(&mut ToStreamContext::new_with_backfill_type(
352 false,
353 BackfillType::Replicated,
354 ))
355 .unwrap();
356
357 let input = stream.as_stream_change_log().unwrap().input();
358 assert!(input.as_stream_exchange().is_some());
359 assert_eq!(input.distribution(), &Distribution::HashShard(vec![0]));
360
361 let changelog = LogicalChangeLog::create(scan.into(), None, None);
363 let pruned = changelog.prune_col(
364 &[1, 2, 3],
365 &mut ColumnPruningContext::new(changelog.clone()),
366 );
367 let stream = pruned
368 .to_stream(&mut ToStreamContext::new_with_backfill_type(
369 false,
370 BackfillType::Replicated,
371 ))
372 .unwrap();
373 let changelog = stream.as_stream_project().unwrap().input();
374 let input = changelog.as_stream_change_log().unwrap().input();
375 assert!(input.as_stream_exchange().is_some());
376 assert_eq!(input.distribution(), &Distribution::HashShard(vec![1]));
377 }
378}