1use std::collections::{BTreeMap, HashMap, HashSet};
16use std::num::NonZeroUsize;
17use std::ops::{Deref, DerefMut};
18use std::sync::LazyLock;
19
20use anyhow::{Context, anyhow};
21use enum_as_inner::EnumAsInner;
22use itertools::Itertools;
23use risingwave_common::bail;
24use risingwave_common::catalog::{
25 CDC_SOURCE_COLUMN_NUM, ColumnCatalog, ColumnId, Field, FragmentTypeFlag, FragmentTypeMask,
26 TableId, generate_internal_table_name_with_type,
27};
28use risingwave_common::hash::VnodeCount;
29use risingwave_common::id::JobId;
30use risingwave_common::util::iter_util::ZipEqFast;
31use risingwave_common::util::stream_graph_visitor::{
32 self, visit_stream_node_cont, visit_stream_node_cont_mut,
33};
34use risingwave_connector::sink::catalog::SinkType;
35use risingwave_meta_model::streaming_job::BackfillOrders;
36use risingwave_pb::catalog::{PbSink, PbTable, Table};
37use risingwave_pb::expr::{ExprNode as PbExprNode, expr_node};
38use risingwave_pb::id::{RelationId, StreamNodeLocalOperatorId};
39use risingwave_pb::plan_common::{PbColumnCatalog, PbColumnDesc};
40use risingwave_pb::stream_plan::dispatch_output_mapping::TypePair;
41use risingwave_pb::stream_plan::stream_fragment_graph::{
42 Parallelism, StreamFragment, StreamFragmentEdge as StreamFragmentEdgeProto,
43};
44use risingwave_pb::stream_plan::stream_node::{NodeBody, PbNodeBody};
45use risingwave_pb::stream_plan::{
46 BackfillOrder, DispatchOutputMapping, DispatchStrategy, DispatcherType, PbStreamNode,
47 PbStreamScanType, StreamFragmentGraph as StreamFragmentGraphProto, StreamNode, StreamScanNode,
48 StreamScanType,
49};
50
51use crate::barrier::SnapshotBackfillInfo;
52use crate::controller::id::IdGeneratorManager;
53use crate::manager::{MetaSrvEnv, StreamingJob, StreamingJobType};
54use crate::model::{Fragment, FragmentDownstreamRelation, FragmentId};
55use crate::stream::stream_graph::id::{GlobalFragmentId, GlobalFragmentIdGen, GlobalTableIdGen};
56use crate::stream::stream_graph::schedule::Distribution;
57use crate::{MetaError, MetaResult};
58
59#[derive(Debug, Clone)]
62pub(super) struct BuildingFragment {
63 inner: StreamFragment,
65
66 job_id: Option<JobId>,
68
69 upstream_job_columns: HashMap<JobId, Vec<PbColumnDesc>>,
74}
75
76impl BuildingFragment {
77 fn new(
80 id: GlobalFragmentId,
81 fragment: StreamFragment,
82 job: &StreamingJob,
83 table_id_gen: GlobalTableIdGen,
84 ) -> Self {
85 let mut fragment = StreamFragment {
86 fragment_id: id.as_global_id(),
87 ..fragment
88 };
89
90 Self::fill_internal_tables(&mut fragment, job, table_id_gen);
92
93 let job_id = Self::fill_job(&mut fragment, job).then(|| job.id());
94 let upstream_job_columns =
95 Self::extract_upstream_columns_except_cross_db_backfill(&fragment);
96
97 Self {
98 inner: fragment,
99 job_id,
100 upstream_job_columns,
101 }
102 }
103
104 fn extract_internal_tables(&self) -> Vec<Table> {
106 let mut fragment = self.inner.clone();
107 let mut tables = Vec::new();
108 stream_graph_visitor::visit_internal_tables(&mut fragment, |table, _| {
109 tables.push(table.clone());
110 });
111 tables
112 }
113
114 fn fill_internal_tables(
116 fragment: &mut StreamFragment,
117 job: &StreamingJob,
118 table_id_gen: GlobalTableIdGen,
119 ) {
120 let fragment_id = fragment.fragment_id;
121 stream_graph_visitor::visit_internal_tables(fragment, |table, table_type_name| {
122 table.id = table_id_gen
123 .to_global_id(table.id.as_raw_id())
124 .as_global_id();
125 table.schema_id = job.schema_id();
126 table.database_id = job.database_id();
127 table.name = generate_internal_table_name_with_type(
128 &job.name(),
129 fragment_id,
130 table.id,
131 table_type_name,
132 );
133 table.fragment_id = fragment_id;
134 table.owner = job.owner();
135 table.job_id = Some(job.id());
136 });
137 }
138
139 fn fill_job(fragment: &mut StreamFragment, job: &StreamingJob) -> bool {
141 let job_id = job.id();
142 let fragment_id = fragment.fragment_id;
143 let mut has_job = false;
144
145 stream_graph_visitor::visit_fragment_mut(fragment, |node_body| match node_body {
146 NodeBody::Materialize(materialize_node) => {
147 materialize_node.table_id = job_id.as_mv_table_id();
148
149 let table = materialize_node.table.insert(job.table().unwrap().clone());
151 table.fragment_id = fragment_id; if cfg!(not(debug_assertions)) {
154 table.definition = job.name();
155 }
156
157 has_job = true;
158 }
159 NodeBody::Sink(sink_node) => {
160 sink_node.sink_desc.as_mut().unwrap().id = job_id.as_sink_id();
161
162 has_job = true;
163 }
164 NodeBody::IcebergWithPkIndexWriter(writer_node) => {
165 writer_node.sink_desc.as_mut().unwrap().id = job_id.as_sink_id();
166
167 has_job = true;
168 }
169 NodeBody::IcebergWithPkIndexPositionDeleteMerger(merger_node) => {
170 merger_node.sink_desc.as_mut().unwrap().id = job_id.as_sink_id();
171
172 has_job = true;
173 }
174 NodeBody::CompactionResolver(resolver_node) => {
175 resolver_node.sink_id = job_id.as_sink_id();
176 }
177 NodeBody::Dml(dml_node) => {
178 dml_node.table_id = job_id.as_mv_table_id();
179 dml_node.table_version_id = job.table_version_id().unwrap();
180 }
181 NodeBody::StreamFsFetch(fs_fetch_node) => {
182 if let StreamingJob::Table(table_source, _, _) = job
183 && let Some(node_inner) = fs_fetch_node.node_inner.as_mut()
184 && let Some(source) = table_source
185 {
186 node_inner.source_id = source.id;
187 if let Some(id) = source.optional_associated_table_id {
188 node_inner.associated_table_id = Some(id.into());
189 }
190 }
191 }
192 NodeBody::Source(source_node) => {
193 match job {
194 StreamingJob::Table(source, _table, _table_job_type) => {
197 if let Some(source_inner) = source_node.source_inner.as_mut()
198 && let Some(source) = source
199 {
200 debug_assert_ne!(source.id, job_id.as_raw_id());
201 source_inner.source_id = source.id;
202 if let Some(id) = source.optional_associated_table_id {
203 source_inner.associated_table_id = Some(id.into());
204 }
205 }
206 }
207 StreamingJob::Source(source) => {
208 has_job = true;
209 if let Some(source_inner) = source_node.source_inner.as_mut() {
210 debug_assert_eq!(source.id, job_id.as_raw_id());
211 source_inner.source_id = source.id;
212 if let Some(id) = source.optional_associated_table_id {
213 source_inner.associated_table_id = Some(id.into());
214 }
215 }
216 }
217 _ => {}
219 }
220 }
221 NodeBody::StreamCdcScan(node) => {
222 if let Some(table_desc) = node.cdc_table_desc.as_mut() {
223 table_desc.table_id = job_id.as_mv_table_id();
224 }
225 }
226 NodeBody::VectorIndexWrite(node) => {
227 let table = node.table.as_mut().unwrap();
228 table.id = job_id.as_mv_table_id();
229 table.database_id = job.database_id();
230 table.schema_id = job.schema_id();
231 table.fragment_id = fragment_id;
232 #[cfg(not(debug_assertions))]
233 {
234 table.definition = job.name();
235 }
236
237 has_job = true;
238 }
239 _ => {}
240 });
241
242 has_job
243 }
244
245 fn extract_upstream_columns_except_cross_db_backfill(
247 fragment: &StreamFragment,
248 ) -> HashMap<JobId, Vec<PbColumnDesc>> {
249 let mut table_columns = HashMap::new();
250
251 stream_graph_visitor::visit_fragment(fragment, |node_body| {
252 let (table_id, column_ids) = match node_body {
253 NodeBody::StreamScan(stream_scan) => {
254 if stream_scan.get_stream_scan_type().unwrap()
255 == StreamScanType::CrossDbSnapshotBackfill
256 {
257 return;
258 }
259 (
260 stream_scan.table_id.as_job_id(),
261 stream_scan.upstream_columns(),
262 )
263 }
264 NodeBody::CdcFilter(cdc_filter) => (
265 cdc_filter.upstream_source_id.as_share_source_job_id(),
266 vec![],
267 ),
268 NodeBody::SourceBackfill(backfill) => (
269 backfill.upstream_source_id.as_share_source_job_id(),
270 backfill.column_descs(),
272 ),
273 _ => return,
274 };
275 table_columns
276 .try_insert(table_id, column_ids)
277 .expect("currently there should be no two same upstream tables in a fragment");
278 });
279
280 table_columns
281 }
282
283 pub fn has_shuffled_backfill(&self) -> bool {
284 let stream_node = match self.inner.node.as_ref() {
285 Some(node) => node,
286 _ => return false,
287 };
288 let mut has_shuffled_backfill = false;
289 let has_shuffled_backfill_mut_ref = &mut has_shuffled_backfill;
290 visit_stream_node_cont(stream_node, |node| {
291 let is_shuffled_backfill = if let Some(node) = &node.node_body
292 && let Some(node) = node.as_stream_scan()
293 {
294 node.stream_scan_type == StreamScanType::ArrangementBackfill as i32
295 || node.stream_scan_type == StreamScanType::SnapshotBackfill as i32
296 } else {
297 false
298 };
299 if is_shuffled_backfill {
300 *has_shuffled_backfill_mut_ref = true;
301 false
302 } else {
303 true
304 }
305 });
306 has_shuffled_backfill
307 }
308}
309
310impl Deref for BuildingFragment {
311 type Target = StreamFragment;
312
313 fn deref(&self) -> &Self::Target {
314 &self.inner
315 }
316}
317
318impl DerefMut for BuildingFragment {
319 fn deref_mut(&mut self) -> &mut Self::Target {
320 &mut self.inner
321 }
322}
323
324#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumAsInner)]
327pub(super) enum EdgeId {
328 Internal {
330 link_id: u64,
333 },
334
335 UpstreamExternal {
338 upstream_job_id: JobId,
340 downstream_fragment_id: GlobalFragmentId,
342 },
343
344 DownstreamExternal(DownstreamExternalEdgeId),
347}
348
349#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
350pub(super) struct DownstreamExternalEdgeId {
351 pub(super) original_upstream_fragment_id: GlobalFragmentId,
353 pub(super) downstream_fragment_id: GlobalFragmentId,
355}
356
357#[derive(Debug, Clone)]
361pub(super) struct StreamFragmentEdge {
362 pub id: EdgeId,
364
365 pub dispatch_strategy: DispatchStrategy,
367}
368
369impl StreamFragmentEdge {
370 fn from_protobuf(edge: &StreamFragmentEdgeProto) -> Self {
371 Self {
372 id: EdgeId::Internal {
375 link_id: edge.link_id,
376 },
377 dispatch_strategy: edge.get_dispatch_strategy().unwrap().clone(),
378 }
379 }
380}
381
382fn clone_fragment(fragment: &Fragment, id_generator_manager: &IdGeneratorManager) -> Fragment {
383 let fragment_id = GlobalFragmentIdGen::new(id_generator_manager, 1)
384 .to_global_id(0)
385 .as_global_id();
386 Fragment {
387 fragment_id,
388 fragment_type_mask: fragment.fragment_type_mask,
389 distribution_type: fragment.distribution_type,
390 state_table_ids: fragment.state_table_ids.clone(),
391 maybe_vnode_count: fragment.maybe_vnode_count,
392 nodes: fragment.nodes.clone(),
393 }
394}
395
396pub fn check_sink_fragments_support_refresh_schema(
397 fragments: &BTreeMap<FragmentId, Fragment>,
398) -> MetaResult<()> {
399 if fragments.len() != 1 {
400 return Err(anyhow!(
401 "sink with auto schema change should have only 1 fragment, but got {:?}",
402 fragments.len()
403 )
404 .into());
405 }
406 let (_, fragment) = fragments.first_key_value().expect("non-empty");
407 let sink_node = &fragment.nodes;
408 let PbNodeBody::Sink(_) = sink_node.node_body.as_ref().unwrap() else {
409 return Err(anyhow!("expect PbNodeBody::Sink but got: {:?}", sink_node.node_body).into());
410 };
411 let [stream_input_node] = sink_node.input.as_slice() else {
412 panic!("Sink has more than 1 input: {:?}", sink_node.input);
413 };
414 let stream_scan_node = match stream_input_node.node_body.as_ref().unwrap() {
415 PbNodeBody::StreamScan(_) => stream_input_node,
416 PbNodeBody::Project(_) => {
417 let [stream_scan_node] = stream_input_node.input.as_slice() else {
418 return Err(anyhow!(
419 "Project node must have exactly 1 input for auto schema change, but got {:?}",
420 stream_input_node.input.len()
421 )
422 .into());
423 };
424 stream_scan_node
425 }
426 _ => {
427 return Err(anyhow!(
428 "expect PbNodeBody::StreamScan or PbNodeBody::Project but got: {:?}",
429 stream_input_node.node_body
430 )
431 .into());
432 }
433 };
434 let PbNodeBody::StreamScan(scan) = stream_scan_node.node_body.as_ref().unwrap() else {
435 return Err(anyhow!(
436 "expect PbNodeBody::StreamScan but got: {:?}",
437 stream_scan_node.node_body
438 )
439 .into());
440 };
441 let stream_scan_type = PbStreamScanType::try_from(scan.stream_scan_type).unwrap();
442 if stream_scan_type != PbStreamScanType::ArrangementBackfill {
443 return Err(anyhow!(
444 "unsupported stream_scan_type for auto refresh schema: {:?}",
445 stream_scan_type
446 )
447 .into());
448 }
449 let [merge_node, _batch_plan_node] = stream_scan_node.input.as_slice() else {
450 panic!(
451 "the number of StreamScan inputs is not 2: {:?}",
452 stream_scan_node.input
453 );
454 };
455 let NodeBody::Merge(_) = merge_node.node_body.as_ref().unwrap() else {
456 return Err(anyhow!(
457 "expect PbNodeBody::Merge but got: {:?}",
458 merge_node.node_body
459 )
460 .into());
461 };
462 Ok(())
463}
464
465struct ScanRewriteResult {
467 old_output_index_to_new_output_index: HashMap<u32, u32>,
468 new_output_index_by_column_id: HashMap<ColumnId, u32>,
469 output_fields: Vec<risingwave_pb::plan_common::Field>,
470}
471
472fn extend_sink_columns(
474 sink_columns: &mut Vec<PbColumnCatalog>,
475 new_columns: &[ColumnCatalog],
476 get_column_name: impl Fn(&String) -> String,
477) {
478 let next_column_id = sink_columns
479 .iter()
480 .map(|col| col.column_desc.as_ref().unwrap().column_id + 1)
481 .max()
482 .unwrap_or(1);
483 sink_columns.extend(new_columns.iter().enumerate().map(|(i, col)| {
484 let mut col = col.to_protobuf();
485 let column_desc = col.column_desc.as_mut().unwrap();
486 column_desc.column_id = next_column_id + (i as i32);
487 column_desc.name = get_column_name(&column_desc.name);
488 col
489 }));
490}
491
492fn build_new_sink_columns(
494 sink: &PbSink,
495 removed_column_names: &HashSet<String>,
496 newly_added_columns: &[ColumnCatalog],
497) -> Vec<PbColumnCatalog> {
498 let mut columns: Vec<PbColumnCatalog> = sink
499 .columns
500 .iter()
501 .filter(|col| {
502 let column_name = &col.column_desc.as_ref().unwrap().name;
503 !removed_column_names.contains(column_name)
504 })
505 .cloned()
506 .collect();
507 extend_sink_columns(&mut columns, newly_added_columns, |name| name.clone());
508 columns
509}
510
511fn rewrite_log_store_table(
513 log_store_table: &mut PbTable,
514 removed_log_store_column_names: &HashSet<String>,
515 newly_added_columns: &[ColumnCatalog],
516 upstream_table_name: &str,
517) {
518 log_store_table.columns.retain(|col| {
519 !removed_log_store_column_names.contains(&col.column_desc.as_ref().unwrap().name)
520 });
521 extend_sink_columns(&mut log_store_table.columns, newly_added_columns, |name| {
522 format!("{}_{}", upstream_table_name, name)
523 });
524 log_store_table.value_indices = (0..log_store_table.columns.len() as i32).collect();
525}
526
527fn rewrite_stream_scan_and_merge(
529 stream_scan_node: &mut StreamNode,
530 removed_column_ids: &HashSet<ColumnId>,
531 newly_added_columns: &[ColumnCatalog],
532 upstream_table: &PbTable,
533 upstream_table_fragment_id: FragmentId,
534) -> MetaResult<ScanRewriteResult> {
535 let PbNodeBody::StreamScan(scan) = stream_scan_node.node_body.as_mut().unwrap() else {
536 return Err(anyhow!(
537 "expect PbNodeBody::StreamScan but got: {:?}",
538 stream_scan_node.node_body
539 )
540 .into());
541 };
542 let [merge_node, _batch_plan_node] = stream_scan_node.input.as_mut_slice() else {
543 panic!(
544 "the number of StreamScan inputs is not 2: {:?}",
545 stream_scan_node.input
546 );
547 };
548 let NodeBody::Merge(merge) = merge_node.node_body.as_mut().unwrap() else {
549 return Err(anyhow!(
550 "expect PbNodeBody::Merge but got: {:?}",
551 merge_node.node_body
552 )
553 .into());
554 };
555
556 let stream_scan_type = PbStreamScanType::try_from(scan.stream_scan_type).unwrap();
557 if stream_scan_type != PbStreamScanType::ArrangementBackfill {
558 return Err(anyhow!(
559 "unsupported stream_scan_type for auto refresh schema: {:?}",
560 stream_scan_type
561 )
562 .into());
563 }
564
565 let upstream_columns_by_id: HashMap<i32, PbColumnDesc> = upstream_table
566 .columns
567 .iter()
568 .map(|col| {
569 let desc = col.column_desc.as_ref().unwrap().clone();
570 (desc.column_id, desc)
571 })
572 .collect();
573
574 let old_upstream_column_ids = scan.upstream_column_ids.clone();
575 let old_output_indices = scan.output_indices.clone();
576 let mut old_upstream_index_to_new_upstream_index = HashMap::new();
577 let mut new_upstream_column_ids = Vec::new();
578 for (old_idx, &column_id) in old_upstream_column_ids.iter().enumerate() {
579 if !removed_column_ids.contains(&ColumnId::new(column_id as _)) {
580 let new_idx = new_upstream_column_ids.len() as u32;
581 old_upstream_index_to_new_upstream_index.insert(old_idx as u32, new_idx);
582 new_upstream_column_ids.push(column_id);
583 }
584 }
585 let mut new_output_indices = Vec::new();
586 for old_output_index in &old_output_indices {
587 if let Some(new_index) = old_upstream_index_to_new_upstream_index.get(old_output_index) {
588 new_output_indices.push(*new_index);
589 }
590 }
591 for col in newly_added_columns {
592 let new_index = new_upstream_column_ids.len() as u32;
593 new_upstream_column_ids.push(col.column_id().get_id());
594 new_output_indices.push(new_index);
595 }
596
597 let new_output_column_ids: Vec<i32> = new_output_indices
598 .iter()
599 .map(|&idx| new_upstream_column_ids[idx as usize])
600 .collect();
601 let mut new_output_index_by_column_id = HashMap::new();
602 for (pos, &column_id) in new_output_column_ids.iter().enumerate() {
603 new_output_index_by_column_id.insert(ColumnId::new(column_id as _), pos as u32);
604 }
605 let mut old_output_index_to_new_output_index = HashMap::new();
606 for (old_pos, old_output_index) in old_output_indices.iter().enumerate() {
607 let column_id = old_upstream_column_ids[*old_output_index as usize];
608 if let Some(new_pos) = new_output_index_by_column_id.get(&ColumnId::new(column_id as _)) {
609 old_output_index_to_new_output_index.insert(old_pos as u32, *new_pos);
610 }
611 }
612
613 scan.arrangement_table = Some(upstream_table.clone());
614 scan.upstream_column_ids = new_upstream_column_ids;
615 scan.output_indices = new_output_indices;
616 let table_desc = scan.table_desc.as_mut().unwrap();
617 table_desc.columns = scan
618 .upstream_column_ids
619 .iter()
620 .map(|column_id| {
621 upstream_columns_by_id
622 .get(column_id)
623 .unwrap_or_else(|| panic!("upstream column id not found: {}", column_id))
624 .clone()
625 })
626 .collect();
627
628 stream_scan_node.fields = new_output_column_ids
629 .iter()
630 .map(|column_id| {
631 let col_desc = upstream_columns_by_id
632 .get(column_id)
633 .unwrap_or_else(|| panic!("upstream column id not found: {}", column_id));
634 Field::new(
635 format!("{}.{}", upstream_table.name, col_desc.name),
636 col_desc.column_type.as_ref().unwrap().into(),
637 )
638 .to_prost()
639 })
640 .collect();
641 stream_scan_node.identity = {
643 let columns = stream_scan_node
644 .fields
645 .iter()
646 .map(|col| &col.name)
647 .join(", ");
648 format!("StreamTableScan {{ table: t, columns: [{columns}] }}")
649 };
650
651 merge_node.fields = scan
653 .upstream_column_ids
654 .iter()
655 .map(|&column_id| {
656 let col_desc = upstream_columns_by_id
657 .get(&column_id)
658 .unwrap_or_else(|| panic!("upstream column id not found: {}", column_id));
659 Field::new(
660 col_desc.name.clone(),
661 col_desc.column_type.as_ref().unwrap().into(),
662 )
663 .to_prost()
664 })
665 .collect();
666 merge.upstream_fragment_id = upstream_table_fragment_id;
667
668 Ok(ScanRewriteResult {
669 old_output_index_to_new_output_index,
670 new_output_index_by_column_id,
671 output_fields: stream_scan_node.fields.clone(),
672 })
673}
674
675fn rewrite_project_node(
677 project_node: &mut StreamNode,
678 scan_rewrite: &ScanRewriteResult,
679 newly_added_columns: &[ColumnCatalog],
680 removed_column_ids: &HashSet<ColumnId>,
681 upstream_table_name: &str,
682) -> MetaResult<()> {
683 let PbNodeBody::Project(project_node_body) = project_node.node_body.as_mut().unwrap() else {
684 return Err(anyhow!(
685 "expect PbNodeBody::Project but got: {:?}",
686 project_node.node_body
687 )
688 .into());
689 };
690 let has_non_input_ref = project_node_body
691 .select_list
692 .iter()
693 .any(|expr| !matches!(expr.rex_node, Some(expr_node::RexNode::InputRef(_))));
694 if has_non_input_ref && !removed_column_ids.is_empty() {
695 return Err(anyhow!(
696 "auto schema change with drop column only supports Project with InputRef"
697 )
698 .into());
699 }
700
701 let mut new_select_list = Vec::with_capacity(project_node_body.select_list.len());
702 let mut new_project_fields = Vec::with_capacity(project_node.fields.len());
703 for (index, expr) in project_node_body.select_list.iter().enumerate() {
704 let mut new_expr = expr.clone();
705 if let Some(expr_node::RexNode::InputRef(old_index)) = new_expr.rex_node {
706 let Some(&new_index) = scan_rewrite
707 .old_output_index_to_new_output_index
708 .get(&old_index)
709 else {
710 continue;
711 };
712 new_expr.rex_node = Some(expr_node::RexNode::InputRef(new_index));
713 } else if !removed_column_ids.is_empty() {
714 return Err(anyhow!(
715 "auto schema change with drop column only supports Project with InputRef"
716 )
717 .into());
718 }
719 new_select_list.push(new_expr);
720 new_project_fields.push(project_node.fields[index].clone());
721 }
722
723 for col in newly_added_columns {
724 let Some(&new_index) = scan_rewrite
725 .new_output_index_by_column_id
726 .get(&col.column_id())
727 else {
728 return Err(anyhow!("new column id not found in scan output").into());
729 };
730 new_select_list.push(PbExprNode {
731 function_type: expr_node::Type::Unspecified as i32,
732 return_type: Some(col.data_type().to_protobuf()),
733 rex_node: Some(expr_node::RexNode::InputRef(new_index)),
734 });
735 new_project_fields.push(
736 Field::new(
737 format!("{}.{}", upstream_table_name, col.column_desc.name),
738 col.data_type().clone(),
739 )
740 .to_prost(),
741 );
742 }
743
744 project_node_body.select_list = new_select_list;
745 project_node.fields = new_project_fields;
746 Ok(())
747}
748
749pub fn rewrite_refresh_schema_sink_fragment(
750 original_sink_fragment: &Fragment,
751 sink: &PbSink,
752 newly_added_columns: &[ColumnCatalog],
753 removed_columns: &[ColumnCatalog],
754 upstream_table: &PbTable,
755 upstream_table_fragment_id: FragmentId,
756 id_generator_manager: &IdGeneratorManager,
757) -> MetaResult<(Fragment, Vec<PbColumnCatalog>, Option<PbTable>)> {
758 let removed_column_ids: HashSet<_> =
759 removed_columns.iter().map(|col| col.column_id()).collect();
760 let removed_log_store_column_names: HashSet<_> = removed_columns
761 .iter()
762 .map(|col| format!("{}_{}", upstream_table.name, col.column_desc.name))
763 .collect();
764 let removed_sink_column_names: HashSet<_> = removed_columns
765 .iter()
766 .map(|col| col.column_desc.name.clone())
767 .collect();
768 let new_sink_columns =
769 build_new_sink_columns(sink, &removed_sink_column_names, newly_added_columns);
770
771 let mut new_sink_fragment = clone_fragment(original_sink_fragment, id_generator_manager);
772 let sink_node = &mut new_sink_fragment.nodes;
773 let PbNodeBody::Sink(sink_node_body) = sink_node.node_body.as_mut().unwrap() else {
774 return Err(anyhow!("expect PbNodeBody::Sink but got: {:?}", sink_node.node_body).into());
775 };
776 let [stream_input_node] = sink_node.input.as_mut_slice() else {
777 panic!("Sink has more than 1 input: {:?}", sink_node.input);
778 };
779 let stream_input_body = stream_input_node.node_body.as_ref().unwrap();
780 let stream_input_is_project = matches!(stream_input_body, PbNodeBody::Project(_));
781 let stream_input_is_scan = matches!(stream_input_body, PbNodeBody::StreamScan(_));
782 if !stream_input_is_project && !stream_input_is_scan {
783 return Err(anyhow!(
784 "expect PbNodeBody::StreamScan or PbNodeBody::Project but got: {:?}",
785 stream_input_body
786 )
787 .into());
788 }
789
790 sink_node.identity = {
793 let sink_type = SinkType::from_proto(sink.sink_type());
794 let sink_type_str = sink_type.type_str();
795 let column_names = new_sink_columns
796 .iter()
797 .map(|col| {
798 ColumnCatalog::from(col.clone())
799 .name_with_hidden()
800 .to_string()
801 })
802 .join(", ");
803 let downstream_pk = if !sink_type.is_append_only() {
804 let downstream_pk = sink
805 .downstream_pk
806 .iter()
807 .map(|i| &sink.columns[*i as usize].column_desc.as_ref().unwrap().name)
808 .collect_vec();
809 format!(", downstream_pk: {downstream_pk:?}")
810 } else {
811 "".to_owned()
812 };
813 format!("StreamSink {{ type: {sink_type_str}, columns: [{column_names}]{downstream_pk} }}")
814 };
815 let new_log_store_table = if let Some(log_store_table) = &mut sink_node_body.table {
816 rewrite_log_store_table(
817 log_store_table,
818 &removed_log_store_column_names,
819 newly_added_columns,
820 &upstream_table.name,
821 );
822 Some(log_store_table.clone())
823 } else {
824 None
825 };
826 sink_node_body.sink_desc.as_mut().unwrap().column_catalogs = new_sink_columns.clone();
827
828 let stream_scan_node = if stream_input_is_project {
829 let [stream_scan_node] = stream_input_node.input.as_mut_slice() else {
830 return Err(anyhow!(
831 "Project node must have exactly 1 input for auto schema change, but got {:?}",
832 stream_input_node.input.len()
833 )
834 .into());
835 };
836 stream_scan_node
837 } else {
838 stream_input_node
839 };
840 let scan_rewrite = rewrite_stream_scan_and_merge(
841 stream_scan_node,
842 &removed_column_ids,
843 newly_added_columns,
844 upstream_table,
845 upstream_table_fragment_id,
846 )?;
847
848 if stream_input_is_project {
849 let [project_node] = sink_node.input.as_mut_slice() else {
850 panic!("Sink has more than 1 input: {:?}", sink_node.input);
851 };
852 rewrite_project_node(
853 project_node,
854 &scan_rewrite,
855 newly_added_columns,
856 &removed_column_ids,
857 &upstream_table.name,
858 )?;
859 sink_node.fields = project_node.fields.clone();
860 } else {
861 sink_node.fields = scan_rewrite.output_fields;
862 }
863 Ok((new_sink_fragment, new_sink_columns, new_log_store_table))
864}
865
866#[derive(Clone, Debug, Default)]
871pub struct FragmentBackfillOrder<const EXTENDED: bool> {
872 inner: HashMap<FragmentId, Vec<FragmentId>>,
873}
874
875impl<const EXTENDED: bool> Deref for FragmentBackfillOrder<EXTENDED> {
876 type Target = HashMap<FragmentId, Vec<FragmentId>>;
877
878 fn deref(&self) -> &Self::Target {
879 &self.inner
880 }
881}
882
883impl UserDefinedFragmentBackfillOrder {
884 pub fn new(inner: HashMap<FragmentId, Vec<FragmentId>>) -> Self {
885 Self { inner }
886 }
887
888 pub fn merge(orders: impl Iterator<Item = Self>) -> Self {
889 Self {
890 inner: orders.flat_map(|order| order.inner).collect(),
891 }
892 }
893
894 pub fn to_meta_model(&self) -> BackfillOrders {
895 self.inner.clone().into()
896 }
897}
898
899pub type UserDefinedFragmentBackfillOrder = FragmentBackfillOrder<false>;
900pub type ExtendedFragmentBackfillOrder = FragmentBackfillOrder<true>;
901
902#[derive(Default, Debug)]
909pub struct StreamFragmentGraph {
910 pub(super) fragments: HashMap<GlobalFragmentId, BuildingFragment>,
912
913 pub(super) downstreams:
915 HashMap<GlobalFragmentId, HashMap<GlobalFragmentId, StreamFragmentEdge>>,
916
917 pub(super) upstreams: HashMap<GlobalFragmentId, HashMap<GlobalFragmentId, StreamFragmentEdge>>,
919
920 dependent_table_ids: HashSet<TableId>,
922
923 specified_parallelism: Option<NonZeroUsize>,
926 specified_backfill_parallelism: Option<NonZeroUsize>,
929
930 max_parallelism: usize,
940
941 backfill_order: BackfillOrder,
943}
944
945impl StreamFragmentGraph {
946 pub fn new(
949 env: &MetaSrvEnv,
950 proto: StreamFragmentGraphProto,
951 job: &StreamingJob,
952 ) -> MetaResult<Self> {
953 let fragment_id_gen =
954 GlobalFragmentIdGen::new(env.id_gen_manager(), proto.fragments.len() as u64);
955 let table_id_gen = GlobalTableIdGen::new(env.id_gen_manager(), proto.table_ids_cnt as u64);
959
960 let fragments: HashMap<_, _> = proto
962 .fragments
963 .into_iter()
964 .map(|(id, fragment)| {
965 let id = fragment_id_gen.to_global_id(id.as_raw_id());
966 let fragment = BuildingFragment::new(id, fragment, job, table_id_gen);
967 (id, fragment)
968 })
969 .collect();
970
971 assert_eq!(
972 fragments
973 .values()
974 .map(|f| f.extract_internal_tables().len() as u32)
975 .sum::<u32>(),
976 proto.table_ids_cnt
977 );
978
979 let mut downstreams = HashMap::new();
981 let mut upstreams = HashMap::new();
982
983 for edge in proto.edges {
984 let upstream_id = fragment_id_gen.to_global_id(edge.upstream_id.as_raw_id());
985 let downstream_id = fragment_id_gen.to_global_id(edge.downstream_id.as_raw_id());
986 let edge = StreamFragmentEdge::from_protobuf(&edge);
987
988 upstreams
989 .entry(downstream_id)
990 .or_insert_with(HashMap::new)
991 .try_insert(upstream_id, edge.clone())
992 .unwrap();
993 downstreams
994 .entry(upstream_id)
995 .or_insert_with(HashMap::new)
996 .try_insert(downstream_id, edge)
997 .unwrap();
998 }
999
1000 let dependent_table_ids = proto.dependent_table_ids.iter().copied().collect();
1003
1004 let specified_parallelism = if let Some(Parallelism { parallelism }) = proto.parallelism {
1005 Some(NonZeroUsize::new(parallelism as usize).context("parallelism should not be 0")?)
1006 } else {
1007 None
1008 };
1009 let specified_backfill_parallelism =
1010 if let Some(Parallelism { parallelism }) = proto.backfill_parallelism {
1011 Some(
1012 NonZeroUsize::new(parallelism as usize)
1013 .context("backfill parallelism should not be 0")?,
1014 )
1015 } else {
1016 None
1017 };
1018
1019 let max_parallelism = proto.max_parallelism as usize;
1020 let backfill_order = proto.backfill_order.unwrap_or(BackfillOrder {
1021 order: Default::default(),
1022 });
1023
1024 Ok(Self {
1025 fragments,
1026 downstreams,
1027 upstreams,
1028 dependent_table_ids,
1029 specified_parallelism,
1030 specified_backfill_parallelism,
1031 max_parallelism,
1032 backfill_order,
1033 })
1034 }
1035
1036 pub fn incomplete_internal_tables(&self) -> BTreeMap<TableId, Table> {
1042 let mut tables = BTreeMap::new();
1043 for fragment in self.fragments.values() {
1044 for table in fragment.extract_internal_tables() {
1045 let table_id = table.id;
1046 tables
1047 .try_insert(table_id, table)
1048 .unwrap_or_else(|_| panic!("duplicated table id `{}`", table_id));
1049 }
1050 }
1051 tables
1052 }
1053
1054 pub fn refill_internal_table_ids(&mut self, table_id_map: HashMap<TableId, TableId>) {
1057 for fragment in self.fragments.values_mut() {
1058 stream_graph_visitor::visit_internal_tables(
1059 &mut fragment.inner,
1060 |table, _table_type_name| {
1061 let target = table_id_map.get(&table.id).cloned().unwrap();
1062 table.id = target;
1063 },
1064 );
1065 }
1066 }
1067
1068 pub fn fit_internal_tables_trivial(
1071 &mut self,
1072 mut old_internal_tables: Vec<Table>,
1073 ) -> MetaResult<()> {
1074 let mut new_internal_table_ids = Vec::new();
1075 for fragment in self.fragments.values() {
1076 for table in &fragment.extract_internal_tables() {
1077 new_internal_table_ids.push(table.id);
1078 }
1079 }
1080
1081 if new_internal_table_ids.len() != old_internal_tables.len() {
1082 bail!(
1083 "Different number of internal tables. New: {}, Old: {}",
1084 new_internal_table_ids.len(),
1085 old_internal_tables.len()
1086 );
1087 }
1088 old_internal_tables.sort_by_key(|t| t.id);
1089 new_internal_table_ids.sort();
1090
1091 let internal_table_id_map = new_internal_table_ids
1092 .into_iter()
1093 .zip_eq_fast(old_internal_tables.into_iter())
1094 .collect::<HashMap<_, _>>();
1095
1096 for fragment in self.fragments.values_mut() {
1099 stream_graph_visitor::visit_internal_tables(
1100 &mut fragment.inner,
1101 |table, _table_type_name| {
1102 let target = internal_table_id_map.get(&table.id).cloned().unwrap();
1104 *table = target;
1105 },
1106 );
1107 }
1108
1109 Ok(())
1110 }
1111
1112 pub fn fit_internal_table_ids_with_mapping(&mut self, mut matches: HashMap<TableId, Table>) {
1114 for fragment in self.fragments.values_mut() {
1115 stream_graph_visitor::visit_internal_tables(
1116 &mut fragment.inner,
1117 |table, _table_type_name| {
1118 let target = matches.remove(&table.id).unwrap_or_else(|| {
1119 panic!("no matching table for table {}({})", table.id, table.name)
1120 });
1121 table.id = target.id;
1122 table.maybe_vnode_count = target.maybe_vnode_count;
1123 },
1124 );
1125 }
1126 }
1127
1128 pub fn fit_snapshot_backfill_epochs(
1129 &mut self,
1130 mut snapshot_backfill_epochs: HashMap<StreamNodeLocalOperatorId, u64>,
1131 ) {
1132 for fragment in self.fragments.values_mut() {
1133 visit_stream_node_cont_mut(fragment.node.as_mut().unwrap(), |node| {
1134 if let PbNodeBody::StreamScan(scan) = node.node_body.as_mut().unwrap()
1135 && let StreamScanType::SnapshotBackfill
1136 | StreamScanType::CrossDbSnapshotBackfill = scan.stream_scan_type()
1137 {
1138 let Some(epoch) = snapshot_backfill_epochs.remove(&node.operator_id) else {
1139 panic!("no snapshot epoch found for node {:?}", node)
1140 };
1141 scan.snapshot_backfill_epoch = Some(epoch);
1142 }
1143 true
1144 })
1145 }
1146 }
1147
1148 pub fn table_fragment_id(&self) -> FragmentId {
1150 Itertools::exactly_one(
1151 self.fragments
1152 .values()
1153 .filter(|b| b.job_id.is_some())
1154 .map(|b| b.fragment_id),
1155 )
1156 .expect(
1157 "require exactly 1 materialize/sink/cdc source node when creating the streaming job",
1158 )
1159 }
1160
1161 pub fn dml_fragment_id(&self) -> Option<FragmentId> {
1163 self.fragments
1164 .values()
1165 .filter(|b| {
1166 FragmentTypeMask::from(b.fragment_type_mask).contains(FragmentTypeFlag::Dml)
1167 })
1168 .map(|b| b.fragment_id)
1169 .at_most_one()
1170 .expect("require at most 1 dml node when creating the streaming job")
1171 }
1172
1173 pub fn dependent_table_ids(&self) -> &HashSet<TableId> {
1175 &self.dependent_table_ids
1176 }
1177
1178 pub fn specified_parallelism(&self) -> Option<NonZeroUsize> {
1180 self.specified_parallelism
1181 }
1182
1183 pub fn specified_backfill_parallelism(&self) -> Option<NonZeroUsize> {
1185 self.specified_backfill_parallelism
1186 }
1187
1188 pub fn max_parallelism(&self) -> usize {
1190 self.max_parallelism
1191 }
1192
1193 fn get_downstreams(
1195 &self,
1196 fragment_id: GlobalFragmentId,
1197 ) -> &HashMap<GlobalFragmentId, StreamFragmentEdge> {
1198 self.downstreams.get(&fragment_id).unwrap_or(&EMPTY_HASHMAP)
1199 }
1200
1201 fn get_upstreams(
1203 &self,
1204 fragment_id: GlobalFragmentId,
1205 ) -> &HashMap<GlobalFragmentId, StreamFragmentEdge> {
1206 self.upstreams.get(&fragment_id).unwrap_or(&EMPTY_HASHMAP)
1207 }
1208
1209 pub fn collect_snapshot_backfill_info(
1210 &self,
1211 ) -> MetaResult<(Option<SnapshotBackfillInfo>, SnapshotBackfillInfo)> {
1212 Self::collect_snapshot_backfill_info_impl(self.fragments.values().map(|fragment| {
1213 (
1214 fragment.node.as_ref().unwrap(),
1215 fragment.fragment_type_mask.into(),
1216 )
1217 }))
1218 }
1219
1220 pub fn collect_snapshot_backfill_info_impl(
1222 fragments: impl IntoIterator<Item = (&PbStreamNode, FragmentTypeMask)>,
1223 ) -> MetaResult<(Option<SnapshotBackfillInfo>, SnapshotBackfillInfo)> {
1224 let mut prev_stream_scan: Option<(Option<SnapshotBackfillInfo>, StreamScanNode)> = None;
1225 let mut cross_db_info = SnapshotBackfillInfo {
1226 upstream_mv_table_id_to_backfill_epoch: Default::default(),
1227 };
1228 let mut result = Ok(());
1229 for (node, fragment_type_mask) in fragments {
1230 visit_stream_node_cont(node, |node| {
1231 if let Some(NodeBody::StreamScan(stream_scan)) = node.node_body.as_ref() {
1232 let stream_scan_type = StreamScanType::try_from(stream_scan.stream_scan_type)
1233 .expect("invalid stream_scan_type");
1234 let is_snapshot_backfill = match stream_scan_type {
1235 StreamScanType::SnapshotBackfill => {
1236 assert!(
1237 fragment_type_mask
1238 .contains(FragmentTypeFlag::SnapshotBackfillStreamScan)
1239 );
1240 true
1241 }
1242 StreamScanType::CrossDbSnapshotBackfill => {
1243 assert!(
1244 fragment_type_mask
1245 .contains(FragmentTypeFlag::CrossDbSnapshotBackfillStreamScan)
1246 );
1247 cross_db_info
1248 .upstream_mv_table_id_to_backfill_epoch
1249 .insert(stream_scan.table_id, stream_scan.snapshot_backfill_epoch);
1250
1251 return true;
1252 }
1253 _ => false,
1254 };
1255
1256 match &mut prev_stream_scan {
1257 Some((prev_snapshot_backfill_info, prev_stream_scan)) => {
1258 match (prev_snapshot_backfill_info, is_snapshot_backfill) {
1259 (Some(prev_snapshot_backfill_info), true) => {
1260 prev_snapshot_backfill_info
1261 .upstream_mv_table_id_to_backfill_epoch
1262 .insert(
1263 stream_scan.table_id,
1264 stream_scan.snapshot_backfill_epoch,
1265 );
1266 true
1267 }
1268 (None, false) => true,
1269 (_, _) => {
1270 result = Err(anyhow!("must be either all snapshot_backfill or no snapshot_backfill. Curr: {stream_scan:?} Prev: {prev_stream_scan:?}").into());
1271 false
1272 }
1273 }
1274 }
1275 None => {
1276 prev_stream_scan = Some((
1277 if is_snapshot_backfill {
1278 Some(SnapshotBackfillInfo {
1279 upstream_mv_table_id_to_backfill_epoch: HashMap::from_iter(
1280 [(
1281 stream_scan.table_id,
1282 stream_scan.snapshot_backfill_epoch,
1283 )],
1284 ),
1285 })
1286 } else {
1287 None
1288 },
1289 *stream_scan.clone(),
1290 ));
1291 true
1292 }
1293 }
1294 } else {
1295 true
1296 }
1297 })
1298 }
1299 result.map(|_| {
1300 (
1301 prev_stream_scan
1302 .map(|(snapshot_backfill_info, _)| snapshot_backfill_info)
1303 .unwrap_or(None),
1304 cross_db_info,
1305 )
1306 })
1307 }
1308
1309 pub fn collect_backfill_mapping(
1311 fragments: impl Iterator<Item = (FragmentId, FragmentTypeMask, &PbStreamNode)>,
1312 ) -> HashMap<RelationId, Vec<FragmentId>> {
1313 let mut mapping = HashMap::new();
1314 for (fragment_id, fragment_type_mask, node) in fragments {
1315 let has_some_scan = fragment_type_mask
1316 .contains_any([FragmentTypeFlag::StreamScan, FragmentTypeFlag::SourceScan]);
1317 if has_some_scan {
1318 visit_stream_node_cont(node, |node| {
1319 match node.node_body.as_ref() {
1320 Some(NodeBody::StreamScan(stream_scan)) => {
1321 let table_id = stream_scan.table_id;
1322 let fragments: &mut Vec<_> =
1323 mapping.entry(table_id.as_relation_id()).or_default();
1324 fragments.push(fragment_id);
1325 false
1327 }
1328 Some(NodeBody::SourceBackfill(source_backfill)) => {
1329 let source_id = source_backfill.upstream_source_id;
1330 let fragments: &mut Vec<_> =
1331 mapping.entry(source_id.as_relation_id()).or_default();
1332 fragments.push(fragment_id);
1333 false
1335 }
1336 _ => true,
1337 }
1338 })
1339 }
1340 }
1341 mapping
1342 }
1343
1344 pub fn create_fragment_backfill_ordering(&self) -> UserDefinedFragmentBackfillOrder {
1348 let mapping =
1349 Self::collect_backfill_mapping(self.fragments.iter().map(|(fragment_id, fragment)| {
1350 (
1351 fragment_id.as_global_id(),
1352 fragment.fragment_type_mask.into(),
1353 fragment.node.as_ref().expect("should exist node"),
1354 )
1355 }));
1356 let mut fragment_ordering: HashMap<FragmentId, Vec<FragmentId>> = HashMap::new();
1357
1358 for (rel_id, downstream_rel_ids) in &self.backfill_order.order {
1360 let fragment_ids = mapping.get(rel_id).unwrap();
1361 for fragment_id in fragment_ids {
1362 let downstream_fragment_ids = downstream_rel_ids
1363 .data
1364 .iter()
1365 .flat_map(|&downstream_rel_id| mapping.get(&downstream_rel_id).unwrap().iter())
1366 .copied()
1367 .collect();
1368 fragment_ordering.insert(*fragment_id, downstream_fragment_ids);
1369 }
1370 }
1371
1372 UserDefinedFragmentBackfillOrder {
1373 inner: fragment_ordering,
1374 }
1375 }
1376
1377 pub fn extend_fragment_backfill_ordering_with_locality_backfill<
1378 'a,
1379 FI: Iterator<Item = (FragmentId, FragmentTypeMask, &'a PbStreamNode)> + 'a,
1380 >(
1381 fragment_ordering: UserDefinedFragmentBackfillOrder,
1382 fragment_downstreams: &FragmentDownstreamRelation,
1383 get_fragments: impl Fn() -> FI,
1384 ) -> ExtendedFragmentBackfillOrder {
1385 let mut fragment_ordering = fragment_ordering.inner;
1386 let mapping = Self::collect_backfill_mapping(get_fragments());
1387 if fragment_ordering.is_empty() {
1390 for value in mapping.values() {
1391 for &fragment_id in value {
1392 fragment_ordering.entry(fragment_id).or_default();
1393 }
1394 }
1395 }
1396
1397 let locality_provider_dependencies = Self::find_locality_provider_dependencies(
1399 get_fragments().map(|(fragment_id, _, node)| (fragment_id, node)),
1400 fragment_downstreams,
1401 );
1402
1403 let backfill_fragments: HashSet<FragmentId> = mapping.values().flatten().copied().collect();
1404
1405 let all_locality_provider_fragments: HashSet<FragmentId> =
1408 locality_provider_dependencies.keys().copied().collect();
1409 let downstream_locality_provider_fragments: HashSet<FragmentId> =
1410 locality_provider_dependencies
1411 .values()
1412 .flatten()
1413 .copied()
1414 .collect();
1415 let locality_provider_root_fragments: Vec<FragmentId> = all_locality_provider_fragments
1416 .difference(&downstream_locality_provider_fragments)
1417 .copied()
1418 .collect();
1419
1420 for &backfill_fragment_id in &backfill_fragments {
1423 fragment_ordering
1424 .entry(backfill_fragment_id)
1425 .or_default()
1426 .extend(locality_provider_root_fragments.iter().copied());
1427 }
1428
1429 for (fragment_id, downstream_fragments) in locality_provider_dependencies {
1431 fragment_ordering
1432 .entry(fragment_id)
1433 .or_default()
1434 .extend(downstream_fragments);
1435 }
1436
1437 for downstream in fragment_ordering.values_mut() {
1441 let mut seen = HashSet::new();
1442 downstream.retain(|id| seen.insert(*id));
1443 }
1444
1445 ExtendedFragmentBackfillOrder {
1446 inner: fragment_ordering,
1447 }
1448 }
1449
1450 pub fn find_locality_provider_fragment_state_table_mapping(
1451 &self,
1452 ) -> HashMap<FragmentId, Vec<TableId>> {
1453 let mut mapping: HashMap<FragmentId, Vec<TableId>> = HashMap::new();
1454
1455 for (fragment_id, fragment) in &self.fragments {
1456 let fragment_id = fragment_id.as_global_id();
1457
1458 if let Some(node) = fragment.node.as_ref() {
1460 let mut state_table_ids = Vec::new();
1461
1462 visit_stream_node_cont(node, |stream_node| {
1463 if let Some(NodeBody::LocalityProvider(locality_provider)) =
1464 stream_node.node_body.as_ref()
1465 {
1466 let state_table_id = locality_provider
1468 .state_table
1469 .as_ref()
1470 .expect("must have state table")
1471 .id;
1472 state_table_ids.push(state_table_id);
1473 false } else {
1475 true }
1477 });
1478
1479 if !state_table_ids.is_empty() {
1480 mapping.insert(fragment_id, state_table_ids);
1481 }
1482 }
1483 }
1484
1485 mapping
1486 }
1487
1488 pub fn find_locality_provider_dependencies<'a>(
1496 fragments_nodes: impl Iterator<Item = (FragmentId, &'a PbStreamNode)>,
1497 fragment_downstreams: &FragmentDownstreamRelation,
1498 ) -> HashMap<FragmentId, Vec<FragmentId>> {
1499 let mut locality_provider_fragments = HashSet::new();
1500 let mut dependencies: HashMap<FragmentId, Vec<FragmentId>> = HashMap::new();
1501
1502 for (fragment_id, node) in fragments_nodes {
1504 let has_locality_provider = Self::fragment_has_locality_provider(node);
1505
1506 if has_locality_provider {
1507 locality_provider_fragments.insert(fragment_id);
1508 dependencies.entry(fragment_id).or_default();
1509 }
1510 }
1511
1512 for &provider_fragment_id in &locality_provider_fragments {
1516 let mut visited = HashSet::new();
1518 let mut downstream_locality_providers = Vec::new();
1519
1520 Self::collect_downstream_locality_providers(
1521 provider_fragment_id,
1522 &locality_provider_fragments,
1523 fragment_downstreams,
1524 &mut visited,
1525 &mut downstream_locality_providers,
1526 );
1527
1528 dependencies
1530 .entry(provider_fragment_id)
1531 .or_default()
1532 .extend(downstream_locality_providers);
1533 }
1534
1535 dependencies
1536 }
1537
1538 fn fragment_has_locality_provider(node: &PbStreamNode) -> bool {
1539 let mut has_locality_provider = false;
1540
1541 {
1542 visit_stream_node_cont(node, |stream_node| {
1543 if let Some(NodeBody::LocalityProvider(_)) = stream_node.node_body.as_ref() {
1544 has_locality_provider = true;
1545 false } else {
1547 true }
1549 });
1550 }
1551
1552 has_locality_provider
1553 }
1554
1555 fn collect_downstream_locality_providers(
1557 current_fragment_id: FragmentId,
1558 locality_provider_fragments: &HashSet<FragmentId>,
1559 fragment_downstreams: &FragmentDownstreamRelation,
1560 visited: &mut HashSet<FragmentId>,
1561 downstream_providers: &mut Vec<FragmentId>,
1562 ) {
1563 if visited.contains(¤t_fragment_id) {
1564 return;
1565 }
1566 visited.insert(current_fragment_id);
1567
1568 for downstream_fragment_id in fragment_downstreams
1570 .get(¤t_fragment_id)
1571 .into_iter()
1572 .flat_map(|downstreams| {
1573 downstreams
1574 .iter()
1575 .map(|downstream| downstream.downstream_fragment_id)
1576 })
1577 {
1578 if locality_provider_fragments.contains(&downstream_fragment_id) {
1580 downstream_providers.push(downstream_fragment_id);
1581 }
1582
1583 Self::collect_downstream_locality_providers(
1585 downstream_fragment_id,
1586 locality_provider_fragments,
1587 fragment_downstreams,
1588 visited,
1589 downstream_providers,
1590 );
1591 }
1592 }
1593}
1594
1595pub fn fill_snapshot_backfill_epoch(
1598 node: &mut StreamNode,
1599 snapshot_backfill_info: Option<&SnapshotBackfillInfo>,
1600 cross_db_snapshot_backfill_info: &SnapshotBackfillInfo,
1601) -> MetaResult<bool> {
1602 let mut result = Ok(());
1603 let mut applied = false;
1604 visit_stream_node_cont_mut(node, |node| {
1605 if let Some(NodeBody::StreamScan(stream_scan)) = node.node_body.as_mut()
1606 && (stream_scan.stream_scan_type == StreamScanType::SnapshotBackfill as i32
1607 || stream_scan.stream_scan_type == StreamScanType::CrossDbSnapshotBackfill as i32)
1608 {
1609 result = try {
1610 let table_id = stream_scan.table_id;
1611 let snapshot_epoch = cross_db_snapshot_backfill_info
1612 .upstream_mv_table_id_to_backfill_epoch
1613 .get(&table_id)
1614 .or_else(|| {
1615 snapshot_backfill_info.and_then(|snapshot_backfill_info| {
1616 snapshot_backfill_info
1617 .upstream_mv_table_id_to_backfill_epoch
1618 .get(&table_id)
1619 })
1620 })
1621 .ok_or_else(|| anyhow!("upstream table id not covered: {}", table_id))?
1622 .ok_or_else(|| anyhow!("upstream table id not set: {}", table_id))?;
1623 if let Some(prev_snapshot_epoch) =
1624 stream_scan.snapshot_backfill_epoch.replace(snapshot_epoch)
1625 {
1626 Err(anyhow!(
1627 "snapshot backfill epoch set again: {} {} {}",
1628 table_id,
1629 prev_snapshot_epoch,
1630 snapshot_epoch
1631 ))?;
1632 }
1633 applied = true;
1634 };
1635 result.is_ok()
1636 } else {
1637 true
1638 }
1639 });
1640 result.map_err(MetaError::from).map(|_| applied)
1641}
1642
1643static EMPTY_HASHMAP: LazyLock<HashMap<GlobalFragmentId, StreamFragmentEdge>> =
1644 LazyLock::new(HashMap::new);
1645
1646#[derive(Debug, Clone, EnumAsInner)]
1649pub(super) enum EitherFragment {
1650 Building(BuildingFragment),
1652
1653 Existing,
1655}
1656
1657#[derive(Debug)]
1666pub struct CompleteStreamFragmentGraph {
1667 building_graph: StreamFragmentGraph,
1669
1670 existing_fragments: HashMap<GlobalFragmentId, Fragment>,
1672
1673 extra_downstreams: HashMap<GlobalFragmentId, HashMap<GlobalFragmentId, StreamFragmentEdge>>,
1675
1676 extra_upstreams: HashMap<GlobalFragmentId, HashMap<GlobalFragmentId, StreamFragmentEdge>>,
1678}
1679
1680pub struct FragmentGraphUpstreamContext {
1681 pub upstream_root_fragments: HashMap<JobId, Fragment>,
1684}
1685
1686pub struct FragmentGraphDownstreamContext {
1687 pub original_root_fragment_id: FragmentId,
1688 pub downstream_fragments: Vec<(DispatcherType, Fragment)>,
1689}
1690
1691impl CompleteStreamFragmentGraph {
1692 #[cfg(test)]
1695 pub fn for_test(graph: StreamFragmentGraph) -> Self {
1696 Self {
1697 building_graph: graph,
1698 existing_fragments: Default::default(),
1699 extra_downstreams: Default::default(),
1700 extra_upstreams: Default::default(),
1701 }
1702 }
1703
1704 pub fn with_upstreams(
1708 graph: StreamFragmentGraph,
1709 upstream_context: FragmentGraphUpstreamContext,
1710 job_type: StreamingJobType,
1711 ) -> MetaResult<Self> {
1712 Self::build_helper(graph, Some(upstream_context), None, job_type)
1713 }
1714
1715 pub fn with_downstreams(
1718 graph: StreamFragmentGraph,
1719 downstream_context: FragmentGraphDownstreamContext,
1720 job_type: StreamingJobType,
1721 ) -> MetaResult<Self> {
1722 Self::build_helper(graph, None, Some(downstream_context), job_type)
1723 }
1724
1725 pub fn with_upstreams_and_downstreams(
1727 graph: StreamFragmentGraph,
1728 upstream_context: FragmentGraphUpstreamContext,
1729 downstream_context: FragmentGraphDownstreamContext,
1730 job_type: StreamingJobType,
1731 ) -> MetaResult<Self> {
1732 Self::build_helper(
1733 graph,
1734 Some(upstream_context),
1735 Some(downstream_context),
1736 job_type,
1737 )
1738 }
1739
1740 fn build_helper(
1742 mut graph: StreamFragmentGraph,
1743 upstream_ctx: Option<FragmentGraphUpstreamContext>,
1744 downstream_ctx: Option<FragmentGraphDownstreamContext>,
1745 job_type: StreamingJobType,
1746 ) -> MetaResult<Self> {
1747 let mut extra_downstreams = HashMap::new();
1748 let mut extra_upstreams = HashMap::new();
1749 let mut existing_fragments = HashMap::new();
1750
1751 if let Some(FragmentGraphUpstreamContext {
1752 upstream_root_fragments,
1753 }) = upstream_ctx
1754 {
1755 for (&id, fragment) in &mut graph.fragments {
1756 let uses_shuffled_backfill = fragment.has_shuffled_backfill();
1757
1758 for (&upstream_job_id, required_columns) in &fragment.upstream_job_columns {
1759 let upstream_fragment = upstream_root_fragments
1760 .get(&upstream_job_id)
1761 .context("upstream fragment not found")?;
1762 let upstream_root_fragment_id =
1763 GlobalFragmentId::new(upstream_fragment.fragment_id);
1764
1765 let edge = if fragment.fragment_type_mask & FragmentTypeFlag::CdcFilter as u32
1766 != 0
1767 {
1768 tracing::debug!(
1769 ?upstream_root_fragment_id,
1770 ?required_columns,
1771 identity = ?fragment.inner.get_node().unwrap().get_identity(),
1772 current_frag_id=?id,
1773 "CdcFilter with upstream source fragment"
1774 );
1775
1776 StreamFragmentEdge {
1777 id: EdgeId::UpstreamExternal {
1778 upstream_job_id,
1779 downstream_fragment_id: id,
1780 },
1781 dispatch_strategy: DispatchStrategy {
1784 r#type: DispatcherType::NoShuffle as _,
1785 dist_key_indices: vec![], output_mapping: DispatchOutputMapping::identical(
1787 CDC_SOURCE_COLUMN_NUM as _,
1788 )
1789 .into(),
1790 },
1791 }
1792 } else {
1793 match job_type {
1794 StreamingJobType::MaterializedView
1796 | StreamingJobType::Sink
1797 | StreamingJobType::Index => {
1798 if upstream_fragment
1801 .fragment_type_mask
1802 .contains(FragmentTypeFlag::Mview)
1803 {
1804 let (dist_key_indices, output_mapping) = {
1806 let mview_node = upstream_fragment
1807 .nodes
1808 .get_node_body()
1809 .unwrap()
1810 .as_materialize()
1811 .unwrap();
1812 let all_columns = mview_node.column_descs();
1813 let dist_key_indices = mview_node.dist_key_indices();
1814 let output_mapping = gen_output_mapping(
1815 required_columns,
1816 &all_columns,
1817 )
1818 .context(
1819 "BUG: column not found in the upstream materialized view",
1820 )?;
1821 (dist_key_indices, output_mapping)
1822 };
1823 let dispatch_strategy = mv_on_mv_dispatch_strategy(
1824 uses_shuffled_backfill,
1825 dist_key_indices,
1826 output_mapping,
1827 );
1828
1829 StreamFragmentEdge {
1830 id: EdgeId::UpstreamExternal {
1831 upstream_job_id,
1832 downstream_fragment_id: id,
1833 },
1834 dispatch_strategy,
1835 }
1836 }
1837 else if upstream_fragment
1840 .fragment_type_mask
1841 .contains(FragmentTypeFlag::Source)
1842 {
1843 let output_mapping = {
1844 let source_node = upstream_fragment
1845 .nodes
1846 .get_node_body()
1847 .unwrap()
1848 .as_source()
1849 .unwrap();
1850
1851 let all_columns = source_node.column_descs().unwrap();
1852 gen_output_mapping(required_columns, &all_columns).context(
1853 "BUG: column not found in the upstream source node",
1854 )?
1855 };
1856
1857 StreamFragmentEdge {
1858 id: EdgeId::UpstreamExternal {
1859 upstream_job_id,
1860 downstream_fragment_id: id,
1861 },
1862 dispatch_strategy: DispatchStrategy {
1865 r#type: DispatcherType::NoShuffle as _,
1866 dist_key_indices: vec![], output_mapping: Some(output_mapping),
1868 },
1869 }
1870 } else {
1871 bail!(
1872 "the upstream fragment should be a MView or Source, got fragment type: {:b}",
1873 upstream_fragment.fragment_type_mask
1874 )
1875 }
1876 }
1877 StreamingJobType::Source | StreamingJobType::Table(_) => {
1878 bail!(
1879 "the streaming job shouldn't have an upstream fragment, job_type: {:?}",
1880 job_type
1881 )
1882 }
1883 }
1884 };
1885
1886 extra_downstreams
1888 .entry(upstream_root_fragment_id)
1889 .or_insert_with(HashMap::new)
1890 .try_insert(id, edge.clone())
1891 .unwrap();
1892 extra_upstreams
1893 .entry(id)
1894 .or_insert_with(HashMap::new)
1895 .try_insert(upstream_root_fragment_id, edge)
1896 .unwrap();
1897 }
1898 }
1899
1900 existing_fragments.extend(
1901 upstream_root_fragments
1902 .into_values()
1903 .map(|f| (GlobalFragmentId::new(f.fragment_id), f)),
1904 );
1905 }
1906
1907 if let Some(FragmentGraphDownstreamContext {
1908 original_root_fragment_id,
1909 downstream_fragments,
1910 }) = downstream_ctx
1911 {
1912 let original_table_fragment_id = GlobalFragmentId::new(original_root_fragment_id);
1913 let table_fragment_id = GlobalFragmentId::new(graph.table_fragment_id());
1914
1915 for (dispatcher_type, fragment) in &downstream_fragments {
1918 let id = GlobalFragmentId::new(fragment.fragment_id);
1919
1920 let output_columns = {
1922 let mut res = None;
1923
1924 stream_graph_visitor::visit_stream_node_body(&fragment.nodes, |node_body| {
1925 let columns = match node_body {
1926 NodeBody::StreamScan(stream_scan) => stream_scan.upstream_columns(),
1927 NodeBody::SourceBackfill(source_backfill) => {
1928 source_backfill.column_descs()
1930 }
1931 _ => return,
1932 };
1933 res = Some(columns);
1934 });
1935
1936 res.context("failed to locate downstream scan")?
1937 };
1938
1939 let table_fragment = graph.fragments.get(&table_fragment_id).unwrap();
1940 let nodes = table_fragment.node.as_ref().unwrap();
1941
1942 let (dist_key_indices, output_mapping) = match job_type {
1943 StreamingJobType::Table(_) | StreamingJobType::MaterializedView => {
1944 let mview_node = nodes.get_node_body().unwrap().as_materialize().unwrap();
1945 let all_columns = mview_node.column_descs();
1946 let dist_key_indices = mview_node.dist_key_indices();
1947 let output_mapping = gen_output_mapping(&output_columns, &all_columns)
1948 .ok_or_else(|| {
1949 MetaError::invalid_parameter(
1950 "unable to drop the column due to \
1951 being referenced by downstream materialized views or sinks",
1952 )
1953 })?;
1954 (dist_key_indices, output_mapping)
1955 }
1956
1957 StreamingJobType::Source => {
1958 let source_node = nodes.get_node_body().unwrap().as_source().unwrap();
1959 let all_columns = source_node.column_descs().unwrap();
1960 let output_mapping = gen_output_mapping(&output_columns, &all_columns)
1961 .ok_or_else(|| {
1962 MetaError::invalid_parameter(
1963 "unable to drop the column due to \
1964 being referenced by downstream materialized views or sinks",
1965 )
1966 })?;
1967 assert_eq!(*dispatcher_type, DispatcherType::NoShuffle);
1968 (
1969 vec![], output_mapping,
1971 )
1972 }
1973
1974 _ => bail!("unsupported job type for replacement: {job_type:?}"),
1975 };
1976
1977 let edge = StreamFragmentEdge {
1978 id: EdgeId::DownstreamExternal(DownstreamExternalEdgeId {
1979 original_upstream_fragment_id: original_table_fragment_id,
1980 downstream_fragment_id: id,
1981 }),
1982 dispatch_strategy: DispatchStrategy {
1983 r#type: *dispatcher_type as i32,
1984 output_mapping: Some(output_mapping),
1985 dist_key_indices,
1986 },
1987 };
1988
1989 extra_downstreams
1990 .entry(table_fragment_id)
1991 .or_insert_with(HashMap::new)
1992 .try_insert(id, edge.clone())
1993 .unwrap();
1994 extra_upstreams
1995 .entry(id)
1996 .or_insert_with(HashMap::new)
1997 .try_insert(table_fragment_id, edge)
1998 .unwrap();
1999 }
2000
2001 existing_fragments.extend(
2002 downstream_fragments
2003 .into_iter()
2004 .map(|(_, f)| (GlobalFragmentId::new(f.fragment_id), f)),
2005 );
2006 }
2007
2008 Ok(Self {
2009 building_graph: graph,
2010 existing_fragments,
2011 extra_downstreams,
2012 extra_upstreams,
2013 })
2014 }
2015}
2016
2017fn gen_output_mapping(
2019 required_columns: &[PbColumnDesc],
2020 upstream_columns: &[PbColumnDesc],
2021) -> Option<DispatchOutputMapping> {
2022 let len = required_columns.len();
2023 let mut indices = vec![0; len];
2024 let mut types = None;
2025
2026 for (i, r) in required_columns.iter().enumerate() {
2027 let (ui, u) = upstream_columns
2028 .iter()
2029 .find_position(|&u| u.column_id == r.column_id)?;
2030 indices[i] = ui as u32;
2031
2032 if u.column_type != r.column_type {
2035 types.get_or_insert_with(|| vec![TypePair::default(); len])[i] = TypePair {
2036 upstream: u.column_type.clone(),
2037 downstream: r.column_type.clone(),
2038 };
2039 }
2040 }
2041
2042 let types = types.unwrap_or(Vec::new());
2044
2045 Some(DispatchOutputMapping { indices, types })
2046}
2047
2048fn mv_on_mv_dispatch_strategy(
2049 uses_shuffled_backfill: bool,
2050 dist_key_indices: Vec<u32>,
2051 output_mapping: DispatchOutputMapping,
2052) -> DispatchStrategy {
2053 if uses_shuffled_backfill {
2054 if !dist_key_indices.is_empty() {
2055 DispatchStrategy {
2056 r#type: DispatcherType::Hash as _,
2057 dist_key_indices,
2058 output_mapping: Some(output_mapping),
2059 }
2060 } else {
2061 DispatchStrategy {
2062 r#type: DispatcherType::Simple as _,
2063 dist_key_indices: vec![], output_mapping: Some(output_mapping),
2065 }
2066 }
2067 } else {
2068 DispatchStrategy {
2069 r#type: DispatcherType::NoShuffle as _,
2070 dist_key_indices: vec![], output_mapping: Some(output_mapping),
2072 }
2073 }
2074}
2075
2076impl CompleteStreamFragmentGraph {
2077 pub(super) fn all_fragment_ids(&self) -> impl Iterator<Item = GlobalFragmentId> + '_ {
2080 self.building_graph
2081 .fragments
2082 .keys()
2083 .chain(self.existing_fragments.keys())
2084 .copied()
2085 }
2086
2087 pub(super) fn all_edges(
2089 &self,
2090 ) -> impl Iterator<Item = (GlobalFragmentId, GlobalFragmentId, &StreamFragmentEdge)> + '_ {
2091 self.building_graph
2092 .downstreams
2093 .iter()
2094 .chain(self.extra_downstreams.iter())
2095 .flat_map(|(&from, tos)| tos.iter().map(move |(&to, edge)| (from, to, edge)))
2096 }
2097
2098 pub(super) fn existing_distribution(&self) -> HashMap<GlobalFragmentId, Distribution> {
2100 self.existing_fragments
2101 .iter()
2102 .map(|(&id, f)| (id, Distribution::from_fragment(f)))
2103 .collect()
2104 }
2105
2106 pub(super) fn topo_order(&self) -> MetaResult<Vec<GlobalFragmentId>> {
2113 let mut topo = Vec::new();
2114 let mut downstream_cnts = HashMap::new();
2115
2116 for fragment_id in self.all_fragment_ids() {
2118 let downstream_cnt = self.get_downstreams(fragment_id).count();
2120 if downstream_cnt == 0 {
2121 topo.push(fragment_id);
2122 } else {
2123 downstream_cnts.insert(fragment_id, downstream_cnt);
2124 }
2125 }
2126
2127 let mut i = 0;
2128 while let Some(&fragment_id) = topo.get(i) {
2129 i += 1;
2130 for (upstream_job_id, _) in self.get_upstreams(fragment_id) {
2132 let downstream_cnt = downstream_cnts.get_mut(&upstream_job_id).unwrap();
2133 *downstream_cnt -= 1;
2134 if *downstream_cnt == 0 {
2135 downstream_cnts.remove(&upstream_job_id);
2136 topo.push(upstream_job_id);
2137 }
2138 }
2139 }
2140
2141 if !downstream_cnts.is_empty() {
2142 bail!("graph is not a DAG");
2144 }
2145
2146 Ok(topo)
2147 }
2148
2149 pub(super) fn seal_fragment(
2152 &self,
2153 id: GlobalFragmentId,
2154 distribution: Distribution,
2155 stream_node: StreamNode,
2156 ) -> Fragment {
2157 let building_fragment = self.get_fragment(id).into_building().unwrap();
2158 let internal_tables = building_fragment.extract_internal_tables();
2159 let BuildingFragment {
2160 inner,
2161 job_id,
2162 upstream_job_columns: _,
2163 } = building_fragment;
2164
2165 let distribution_type = distribution.to_distribution_type();
2166 let vnode_count = distribution.vnode_count();
2167
2168 let materialized_fragment_id =
2169 if FragmentTypeMask::from(inner.fragment_type_mask).contains(FragmentTypeFlag::Mview) {
2170 job_id.map(JobId::as_mv_table_id)
2171 } else {
2172 None
2173 };
2174
2175 let vector_index_fragment_id =
2176 if inner.fragment_type_mask & FragmentTypeFlag::VectorIndexWrite as u32 != 0 {
2177 job_id.map(JobId::as_mv_table_id)
2178 } else {
2179 None
2180 };
2181
2182 let state_table_ids = internal_tables
2183 .iter()
2184 .map(|t| t.id)
2185 .chain(materialized_fragment_id)
2186 .chain(vector_index_fragment_id)
2187 .collect();
2188
2189 Fragment {
2190 fragment_id: inner.fragment_id,
2191 fragment_type_mask: inner.fragment_type_mask.into(),
2192 distribution_type,
2193 state_table_ids,
2194 maybe_vnode_count: VnodeCount::set(vnode_count).to_protobuf(),
2195 nodes: stream_node,
2196 }
2197 }
2198
2199 pub(super) fn get_fragment(&self, fragment_id: GlobalFragmentId) -> EitherFragment {
2202 if self.existing_fragments.contains_key(&fragment_id) {
2203 EitherFragment::Existing
2204 } else {
2205 EitherFragment::Building(
2206 self.building_graph
2207 .fragments
2208 .get(&fragment_id)
2209 .unwrap()
2210 .clone(),
2211 )
2212 }
2213 }
2214
2215 pub(super) fn get_downstreams(
2218 &self,
2219 fragment_id: GlobalFragmentId,
2220 ) -> impl Iterator<Item = (GlobalFragmentId, &StreamFragmentEdge)> {
2221 self.building_graph
2222 .get_downstreams(fragment_id)
2223 .iter()
2224 .chain(
2225 self.extra_downstreams
2226 .get(&fragment_id)
2227 .into_iter()
2228 .flatten(),
2229 )
2230 .map(|(&id, edge)| (id, edge))
2231 }
2232
2233 pub(super) fn get_upstreams(
2236 &self,
2237 fragment_id: GlobalFragmentId,
2238 ) -> impl Iterator<Item = (GlobalFragmentId, &StreamFragmentEdge)> {
2239 self.building_graph
2240 .get_upstreams(fragment_id)
2241 .iter()
2242 .chain(self.extra_upstreams.get(&fragment_id).into_iter().flatten())
2243 .map(|(&id, edge)| (id, edge))
2244 }
2245
2246 pub(super) fn building_fragments(&self) -> &HashMap<GlobalFragmentId, BuildingFragment> {
2248 &self.building_graph.fragments
2249 }
2250
2251 pub(super) fn building_fragments_mut(
2253 &mut self,
2254 ) -> &mut HashMap<GlobalFragmentId, BuildingFragment> {
2255 &mut self.building_graph.fragments
2256 }
2257
2258 pub(super) fn max_parallelism(&self) -> usize {
2260 self.building_graph.max_parallelism()
2261 }
2262}
2263
2264#[cfg(test)]
2265mod tests {
2266 use risingwave_common::catalog::{ColumnDesc, ColumnId};
2267 use risingwave_common::types::DataType;
2268 use risingwave_pb::catalog::SinkType as PbSinkType;
2269 use risingwave_pb::meta::table_fragments::fragment::PbFragmentDistributionType;
2270 use risingwave_pb::plan_common::StorageTableDesc;
2271 use risingwave_pb::stream_plan::{
2272 BatchPlanNode, MergeNode, ProjectNode, SinkDesc, SinkLogStoreType, SinkNode, StreamNode,
2273 StreamScanNode, StreamScanType,
2274 };
2275
2276 use super::*;
2277
2278 fn make_column(name: &str, id: i32, data_type: DataType) -> ColumnCatalog {
2279 ColumnCatalog::visible(ColumnDesc::named(name, ColumnId::new(id), data_type))
2280 }
2281
2282 fn make_field(table_name: &str, column: &ColumnCatalog) -> risingwave_pb::plan_common::Field {
2283 Field::new(
2284 format!("{}.{}", table_name, column.column_desc.name),
2285 column.data_type().clone(),
2286 )
2287 .to_prost()
2288 }
2289
2290 fn make_input_ref(index: u32, data_type: &DataType) -> PbExprNode {
2291 PbExprNode {
2292 function_type: expr_node::Type::Unspecified as i32,
2293 return_type: Some(data_type.to_protobuf()),
2294 rex_node: Some(expr_node::RexNode::InputRef(index)),
2295 }
2296 }
2297
2298 fn make_stream_scan_node(
2299 table_name: &str,
2300 table_id: u32,
2301 columns: &[ColumnCatalog],
2302 ) -> StreamNode {
2303 let merge_node = StreamNode {
2304 node_body: Some(NodeBody::Merge(Box::new(MergeNode {
2305 upstream_fragment_id: 0.into(),
2306 ..Default::default()
2307 }))),
2308 fields: columns
2309 .iter()
2310 .map(|col| make_field(table_name, col))
2311 .collect(),
2312 ..Default::default()
2313 };
2314 let batch_plan_node = StreamNode {
2315 node_body: Some(NodeBody::BatchPlan(Box::new(BatchPlanNode {
2316 ..Default::default()
2317 }))),
2318 ..Default::default()
2319 };
2320 let stream_scan_node = StreamScanNode {
2321 table_id: table_id.into(),
2322 upstream_column_ids: columns.iter().map(|c| c.column_id().get_id()).collect(),
2323 output_indices: (0..columns.len()).map(|i| i as u32).collect(),
2324 stream_scan_type: StreamScanType::ArrangementBackfill as i32,
2325 table_desc: Some(StorageTableDesc {
2326 table_id: table_id.into(),
2327 columns: columns
2328 .iter()
2329 .map(|col| col.column_desc.to_protobuf())
2330 .collect(),
2331 value_indices: (0..columns.len()).map(|i| i as u32).collect(),
2332 versioned: true,
2333 ..Default::default()
2334 }),
2335 ..Default::default()
2336 };
2337 StreamNode {
2338 node_body: Some(NodeBody::StreamScan(Box::new(stream_scan_node))),
2339 fields: columns
2340 .iter()
2341 .map(|col| make_field(table_name, col))
2342 .collect(),
2343 input: vec![merge_node, batch_plan_node],
2344 ..Default::default()
2345 }
2346 }
2347
2348 fn make_project_node(
2349 table_name: &str,
2350 columns: &[ColumnCatalog],
2351 input: StreamNode,
2352 ) -> StreamNode {
2353 let select_list = columns
2354 .iter()
2355 .enumerate()
2356 .map(|(i, col)| make_input_ref(i as u32, col.data_type()))
2357 .collect();
2358 StreamNode {
2359 node_body: Some(NodeBody::Project(Box::new(ProjectNode {
2360 select_list,
2361 ..Default::default()
2362 }))),
2363 fields: columns
2364 .iter()
2365 .map(|col| make_field(table_name, col))
2366 .collect(),
2367 input: vec![input],
2368 ..Default::default()
2369 }
2370 }
2371
2372 #[tokio::test]
2373 async fn test_rewrite_refresh_schema_sink_fragment_with_project() {
2374 let env = MetaSrvEnv::for_test().await;
2375 let id_gen_manager = env.id_gen_manager().as_ref();
2376
2377 let table_name = "t";
2378 let columns = vec![
2379 make_column("a", 1, DataType::Int64),
2380 make_column("b", 2, DataType::Int64),
2381 ];
2382 let new_column = make_column("c", 3, DataType::Varchar);
2383
2384 let mut upstream_columns = columns.clone();
2385 upstream_columns.push(new_column.clone());
2386 let upstream_table = PbTable {
2387 name: table_name.to_owned(),
2388 columns: upstream_columns
2389 .iter()
2390 .map(|col| col.to_protobuf())
2391 .collect(),
2392 ..Default::default()
2393 };
2394
2395 let sink = PbSink {
2396 columns: columns.iter().map(|col| col.to_protobuf()).collect(),
2397 sink_type: PbSinkType::AppendOnly as i32,
2398 ..Default::default()
2399 };
2400
2401 let sink_desc = SinkDesc {
2402 sink_type: PbSinkType::AppendOnly as i32,
2403 column_catalogs: sink.columns.clone(),
2404 ..Default::default()
2405 };
2406
2407 let stream_scan_node = make_stream_scan_node(table_name, 1, &columns);
2408 let project_node = make_project_node(table_name, &columns, stream_scan_node);
2409
2410 let log_store_table = PbTable {
2411 columns: columns
2412 .iter()
2413 .cloned()
2414 .map(|mut col| {
2415 col.column_desc.name = format!("{}_{}", table_name, col.column_desc.name);
2416 col.to_protobuf()
2417 })
2418 .collect(),
2419 value_indices: (0..columns.len()).map(|i| i as i32).collect(),
2420 ..Default::default()
2421 };
2422
2423 let original_fragment = Fragment {
2424 fragment_id: 1.into(),
2425 fragment_type_mask: FragmentTypeMask::default(),
2426 distribution_type: PbFragmentDistributionType::Single,
2427 state_table_ids: vec![],
2428 maybe_vnode_count: None,
2429 nodes: StreamNode {
2430 node_body: Some(NodeBody::Sink(Box::new(SinkNode {
2431 sink_desc: Some(sink_desc),
2432 table: Some(log_store_table),
2433 ..Default::default()
2434 }))),
2435 fields: columns
2436 .iter()
2437 .map(|col| make_field(table_name, col))
2438 .collect(),
2439 input: vec![project_node],
2440 ..Default::default()
2441 },
2442 };
2443
2444 let (new_fragment, _, _) = rewrite_refresh_schema_sink_fragment(
2445 &original_fragment,
2446 &sink,
2447 std::slice::from_ref(&new_column),
2448 &[],
2449 &upstream_table,
2450 7.into(),
2451 id_gen_manager,
2452 )
2453 .unwrap();
2454
2455 let sink_node = &new_fragment.nodes;
2456 let [project_node] = sink_node.input.as_slice() else {
2457 panic!("Sink has more than 1 input: {:?}", sink_node.input);
2458 };
2459 let PbNodeBody::Project(project_body) = project_node.node_body.as_ref().unwrap() else {
2460 panic!(
2461 "expect PbNodeBody::Project but got: {:?}",
2462 project_node.node_body
2463 );
2464 };
2465 assert_eq!(project_body.select_list.len(), columns.len() + 1);
2466 let last_expr = project_body.select_list.last().unwrap();
2467 assert!(
2468 matches!(last_expr.rex_node, Some(expr_node::RexNode::InputRef(idx)) if idx == columns.len() as u32)
2469 );
2470 assert_eq!(project_node.fields.len(), columns.len() + 1);
2471
2472 let [stream_scan_node] = project_node.input.as_slice() else {
2473 panic!("Project has more than 1 input: {:?}", project_node.input);
2474 };
2475 let PbNodeBody::StreamScan(scan) = stream_scan_node.node_body.as_ref().unwrap() else {
2476 panic!(
2477 "expect PbNodeBody::StreamScan but got: {:?}",
2478 stream_scan_node.node_body
2479 );
2480 };
2481 assert_eq!(
2482 scan.upstream_column_ids.last().copied(),
2483 Some(new_column.column_id().get_id())
2484 );
2485 assert_eq!(
2486 scan.output_indices.last().copied(),
2487 Some(columns.len() as u32)
2488 );
2489 assert_eq!(
2490 stream_scan_node.fields.last().unwrap().name,
2491 format!("{}.{}", table_name, new_column.column_desc.name)
2492 );
2493 }
2494
2495 #[tokio::test]
2496 async fn test_rewrite_refresh_schema_sink_fragment_drop_column_with_project() {
2497 let env = MetaSrvEnv::for_test().await;
2498 let id_gen_manager = env.id_gen_manager().as_ref();
2499
2500 let table_name = "t";
2501 let columns = vec![
2502 make_column("a", 1, DataType::Int64),
2503 make_column("b", 2, DataType::Int64),
2504 make_column("tmp", 3, DataType::Varchar),
2505 ];
2506 let removed_column = columns.last().unwrap().clone();
2507 let upstream_columns = columns[..2].to_vec();
2508
2509 let upstream_table = PbTable {
2510 name: table_name.to_owned(),
2511 columns: upstream_columns
2512 .iter()
2513 .map(|col| col.to_protobuf())
2514 .collect(),
2515 ..Default::default()
2516 };
2517
2518 let sink = PbSink {
2519 columns: columns.iter().map(|col| col.to_protobuf()).collect(),
2520 sink_type: PbSinkType::AppendOnly as i32,
2521 ..Default::default()
2522 };
2523
2524 let sink_desc = SinkDesc {
2525 sink_type: PbSinkType::AppendOnly as i32,
2526 column_catalogs: sink.columns.clone(),
2527 ..Default::default()
2528 };
2529
2530 let stream_scan_node = make_stream_scan_node(table_name, 1, &columns);
2531 let project_node = make_project_node(table_name, &columns, stream_scan_node);
2532
2533 let log_store_table = PbTable {
2534 columns: columns
2535 .iter()
2536 .cloned()
2537 .map(|mut col| {
2538 col.column_desc.name = format!("{}_{}", table_name, col.column_desc.name);
2539 col.to_protobuf()
2540 })
2541 .collect(),
2542 value_indices: (0..columns.len()).map(|i| i as i32).collect(),
2543 ..Default::default()
2544 };
2545
2546 let original_fragment = Fragment {
2547 fragment_id: 1.into(),
2548 fragment_type_mask: FragmentTypeMask::default(),
2549 distribution_type: PbFragmentDistributionType::Single,
2550 state_table_ids: vec![],
2551 maybe_vnode_count: None,
2552 nodes: StreamNode {
2553 node_body: Some(NodeBody::Sink(Box::new(SinkNode {
2554 sink_desc: Some(sink_desc),
2555 table: Some(log_store_table),
2556 log_store_type: SinkLogStoreType::KvLogStore as i32,
2557 ..Default::default()
2558 }))),
2559 fields: columns
2560 .iter()
2561 .map(|col| make_field(table_name, col))
2562 .collect(),
2563 input: vec![project_node],
2564 ..Default::default()
2565 },
2566 };
2567
2568 let (new_fragment, new_schema, new_log_store_table) = rewrite_refresh_schema_sink_fragment(
2569 &original_fragment,
2570 &sink,
2571 &[],
2572 std::slice::from_ref(&removed_column),
2573 &upstream_table,
2574 7.into(),
2575 id_gen_manager,
2576 )
2577 .unwrap();
2578
2579 assert_eq!(new_schema.len(), 2);
2580 assert!(
2581 new_schema.iter().all(|col| {
2582 col.column_desc.as_ref().map(|desc| desc.name.as_str()) != Some("tmp")
2583 })
2584 );
2585
2586 let sink_node = &new_fragment.nodes;
2587 let [project_node] = sink_node.input.as_slice() else {
2588 panic!("Sink has more than 1 input: {:?}", sink_node.input);
2589 };
2590 let PbNodeBody::Project(project_body) = project_node.node_body.as_ref().unwrap() else {
2591 panic!(
2592 "expect PbNodeBody::Project but got: {:?}",
2593 project_node.node_body
2594 );
2595 };
2596 assert_eq!(project_body.select_list.len(), 2);
2597 assert!(project_node.fields.iter().all(|f| !f.name.contains("tmp")));
2598
2599 let [stream_scan_node] = project_node.input.as_slice() else {
2600 panic!("Project has more than 1 input: {:?}", project_node.input);
2601 };
2602 let PbNodeBody::StreamScan(scan) = stream_scan_node.node_body.as_ref().unwrap() else {
2603 panic!(
2604 "expect PbNodeBody::StreamScan but got: {:?}",
2605 stream_scan_node.node_body
2606 );
2607 };
2608 assert!(
2609 !scan
2610 .upstream_column_ids
2611 .iter()
2612 .any(|&id| id == removed_column.column_id().get_id())
2613 );
2614 assert!(
2615 stream_scan_node
2616 .fields
2617 .iter()
2618 .all(|f| !f.name.contains("tmp"))
2619 );
2620
2621 let new_log_store_table = new_log_store_table.expect("log store table should be updated");
2622 assert!(
2623 new_log_store_table.columns.iter().all(|col| !col
2624 .column_desc
2625 .as_ref()
2626 .unwrap()
2627 .name
2628 .contains("tmp"))
2629 );
2630 assert_eq!(
2631 new_log_store_table.value_indices,
2632 (0..new_log_store_table.columns.len() as i32).collect::<Vec<_>>()
2633 );
2634 }
2635}