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