1mod graph;
16use anyhow::Context;
17use graph::*;
18use risingwave_common::util::recursive::{self, Recurse as _};
19use risingwave_connector::WithPropertiesExt;
20use risingwave_pb::stream_plan::stream_node::NodeBody;
21mod parallelism;
22mod rewrite;
23
24use std::collections::{HashMap, HashSet};
25use std::ops::Deref;
26use std::rc::Rc;
27
28use educe::Educe;
29use risingwave_common::catalog::{FragmentTypeFlag, TableId};
30use risingwave_common::session_config::SessionConfig;
31use risingwave_common::session_config::parallelism::ConfigParallelism;
32use risingwave_common::system_param::AdaptiveParallelismStrategy;
33use risingwave_common::types::DataType;
34use risingwave_common::util::stream_graph_visitor::visit_stream_node_internal_tables;
35use risingwave_connector::source::cdc::CdcScanOptions;
36use risingwave_pb::id::{LocalOperatorId, StreamNodeLocalOperatorId};
37use risingwave_pb::plan_common::JoinType;
38use risingwave_pb::stream_plan::{
39 BackfillOrder, DispatchStrategy, DispatcherType, ExchangeNode, NoOpNode,
40 PbDispatchOutputMapping, StreamContext, StreamFragmentGraph as StreamFragmentGraphProto,
41 StreamNode, StreamScanType,
42};
43
44use self::rewrite::build_delta_join_without_arrange;
45use crate::catalog::FragmentId;
46use crate::error::ErrorCode::NotSupported;
47use crate::error::{Result, RwError};
48use crate::optimizer::plan_node::generic::GenericPlanRef;
49use crate::optimizer::plan_node::{StreamPlanRef as PlanRef, reorganize_elements_id};
50use crate::optimizer::variant_key::variant_key_error;
51use crate::stream_fragmenter::parallelism::{
52 ResolvedParallelism, derive_backfill_parallelism, derive_parallelism,
53};
54
55#[derive(Educe)]
57#[educe(Default)]
58pub struct BuildFragmentGraphState {
59 fragment_graph: StreamFragmentGraph,
61 next_local_fragment_id: FragmentId,
63
64 next_table_id: u32,
67
68 #[educe(Default(expression = u32::MAX - 1))]
70 next_operator_id: u32,
71
72 dependent_table_ids: HashSet<TableId>,
74
75 share_mapping: HashMap<StreamNodeLocalOperatorId, LocalFragmentId>,
77 share_stream_node_mapping: HashMap<StreamNodeLocalOperatorId, StreamNode>,
79
80 has_source_backfill: bool,
81 has_snapshot_backfill: bool,
82 has_cross_db_snapshot_backfill: bool,
83 has_any_backfill: bool,
84}
85
86impl BuildFragmentGraphState {
87 fn new_stream_fragment(&mut self) -> StreamFragment {
89 let fragment = StreamFragment::new(self.next_local_fragment_id);
90 self.next_local_fragment_id += 1;
91 fragment
92 }
93
94 fn gen_operator_id(&mut self) -> StreamNodeLocalOperatorId {
96 self.next_operator_id -= 1;
97 LocalOperatorId::new(self.next_operator_id).into()
98 }
99
100 pub fn gen_table_id(&mut self) -> u32 {
102 let ret = self.next_table_id;
103 self.next_table_id += 1;
104 ret
105 }
106
107 pub fn gen_table_id_wrapped(&mut self) -> TableId {
109 TableId::new(self.gen_table_id())
110 }
111
112 pub fn add_share_stream_node(
113 &mut self,
114 operator_id: StreamNodeLocalOperatorId,
115 stream_node: StreamNode,
116 ) {
117 self.share_stream_node_mapping
118 .insert(operator_id, stream_node);
119 }
120
121 pub fn get_share_stream_node(
122 &mut self,
123 operator_id: StreamNodeLocalOperatorId,
124 ) -> Option<&StreamNode> {
125 self.share_stream_node_mapping.get(&operator_id)
126 }
127
128 pub fn gen_no_op_stream_node(&mut self, input: StreamNode) -> StreamNode {
131 StreamNode {
132 operator_id: self.gen_operator_id(),
133 identity: "StreamNoOp".into(),
134 node_body: Some(NodeBody::NoOp(NoOpNode {})),
135
136 stream_key: input.stream_key.clone(),
138 stream_kind: input.stream_kind,
139 fields: input.fields.clone(),
140
141 input: vec![input],
142 }
143 }
144}
145
146#[derive(Clone, Copy, Debug, PartialEq, Eq)]
148pub enum GraphJobType {
149 Table,
150 MaterializedView,
151 Source,
152 Sink,
153 Index,
154}
155
156impl GraphJobType {
157 pub fn to_parallelism(self, config: &SessionConfig) -> ConfigParallelism {
158 match self {
159 GraphJobType::Table => config.streaming_parallelism_for_table(),
160 GraphJobType::MaterializedView => config.streaming_parallelism_for_materialized_view(),
161 GraphJobType::Source => config.streaming_parallelism_for_source(),
162 GraphJobType::Sink => config.streaming_parallelism_for_sink(),
163 GraphJobType::Index => config.streaming_parallelism_for_index(),
164 }
165 }
166}
167
168pub fn build_graph(
169 plan_node: PlanRef,
170 job_type: Option<GraphJobType>,
171) -> Result<StreamFragmentGraphProto> {
172 build_graph_with_strategy(plan_node, job_type, None)
173}
174
175pub fn build_graph_with_strategy(
176 plan_node: PlanRef,
177 job_type: Option<GraphJobType>,
178 backfill_order: Option<BackfillOrder>,
179) -> Result<StreamFragmentGraphProto> {
180 let ctx = plan_node.plan_base().ctx();
181 let plan_node = reorganize_elements_id(plan_node);
182
183 let mut state = BuildFragmentGraphState::default();
184 let mut stream_node = plan_node.to_stream_prost(&mut state)?;
185 reject_variant_in_internal_storage_key(&mut stream_node)?;
186 generate_fragment_graph(&mut state, stream_node)?;
187 if state.has_source_backfill && state.has_snapshot_backfill {
188 return Err(RwError::from(NotSupported(
189 "Snapshot backfill with shared source backfill is not supported".to_owned(),
190 "`SET streaming_use_shared_source = false` to disable shared source backfill, or \
191 `SET streaming_use_snapshot_backfill = false` to disable snapshot backfill"
192 .to_owned(),
193 )));
194 }
195 if state.has_cross_db_snapshot_backfill
196 && let Some(ref backfill_order) = backfill_order
197 && !backfill_order.order.is_empty()
198 {
199 return Err(RwError::from(NotSupported(
200 "Backfill order control with cross-db snapshot backfill is not supported".to_owned(),
201 "Please remove backfill order specification from your query".to_owned(),
202 )));
203 }
204
205 let (
206 normal_parallelism,
207 backfill_parallelism,
208 adaptive_parallelism_strategy,
209 backfill_adaptive_parallelism_strategy,
210 max_parallelism,
211 ) = {
212 let config = ctx.session_ctx().config();
213 let streaming_parallelism = config.streaming_parallelism();
214 let job_parallelism = job_type.map(|t| t.to_parallelism(config.deref()));
215 let normal_parallelism =
216 derive_parallelism(job_type, job_parallelism, streaming_parallelism);
217 let backfill_parallelism = if state.has_any_backfill {
218 derive_backfill_parallelism(config.streaming_parallelism_for_backfill())
219 } else {
220 ResolvedParallelism {
221 parallelism: None,
222 adaptive_strategy: None,
223 }
224 };
225 (
226 normal_parallelism.parallelism,
227 backfill_parallelism.parallelism,
228 normal_parallelism
229 .adaptive_strategy
230 .as_ref()
231 .map(AdaptiveParallelismStrategy::to_string)
232 .unwrap_or_default(),
233 backfill_parallelism
234 .adaptive_strategy
235 .as_ref()
236 .map(AdaptiveParallelismStrategy::to_string)
237 .unwrap_or_default(),
238 config.streaming_max_parallelism() as _,
239 )
240 };
241
242 let config_override = ctx
243 .session_ctx()
244 .config()
245 .to_initial_streaming_config_override()
246 .context("invalid initial streaming config override")?;
247 let fragments = state
248 .fragment_graph
249 .fragments
250 .into_iter()
251 .map(|(k, v)| (k, v.to_protobuf()))
252 .collect();
253 let edges = state.fragment_graph.edges.into_values().collect();
254
255 Ok(StreamFragmentGraphProto {
256 fragments,
257 edges,
258 dependent_table_ids: state.dependent_table_ids.into_iter().collect(),
259 table_ids_cnt: state.next_table_id,
260 ctx: Some(StreamContext {
261 timezone: ctx.get_session_timezone(),
262 config_override,
263 }),
264 parallelism: normal_parallelism,
265 backfill_parallelism,
266 adaptive_parallelism_strategy,
267 backfill_adaptive_parallelism_strategy,
268 max_parallelism,
269 backfill_order,
270 })
271}
272
273fn reject_variant_in_internal_storage_key(stream_node: &mut StreamNode) -> Result<()> {
277 let mut err = None;
278 visit_stream_node_internal_tables(stream_node, |table, table_name| {
279 if err.is_some() {
280 return;
281 }
282 for order in &table.pk {
283 let column = &table.columns[order.column_index as usize];
284 let column_desc = column.column_desc.as_ref().unwrap();
285 let data_type: DataType = column_desc.column_type.as_ref().unwrap().into();
286 if data_type.contains_variant() {
287 err = Some(variant_key_error(format!(
288 "VARIANT column \"{}\" is part of the storage primary key of the internal \
289 state table of `{}`",
290 column_desc.name, table_name,
291 )));
292 return;
293 }
294 }
295 });
296 match err {
297 Some(err) => Err(err),
298 None => Ok(()),
299 }
300}
301
302#[cfg(any())]
303fn is_stateful_executor(stream_node: &StreamNode) -> bool {
304 matches!(
305 stream_node.get_node_body().unwrap(),
306 NodeBody::HashAgg(_)
307 | NodeBody::HashJoin(_)
308 | NodeBody::DeltaIndexJoin(_)
309 | NodeBody::StreamScan(_)
310 | NodeBody::StreamCdcScan(_)
311 | NodeBody::DynamicFilter(_)
312 )
313}
314
315#[cfg(any())]
320fn rewrite_stream_node(
321 state: &mut BuildFragmentGraphState,
322 stream_node: StreamNode,
323 insert_exchange_flag: bool,
324) -> Result<StreamNode> {
325 let f = |child| {
326 if is_stateful_executor(&child) {
329 if insert_exchange_flag {
330 let child_node = rewrite_stream_node(state, child, true)?;
331
332 let strategy = DispatchStrategy {
333 r#type: DispatcherType::NoShuffle.into(),
334 dist_key_indices: vec![], output_indices: (0..(child_node.fields.len() as u32)).collect(),
336 };
337 Ok(StreamNode {
338 stream_key: child_node.stream_key.clone(),
339 fields: child_node.fields.clone(),
340 node_body: Some(NodeBody::Exchange(ExchangeNode {
341 strategy: Some(strategy),
342 })),
343 operator_id: state.gen_operator_id(),
344 append_only: child_node.append_only,
345 input: vec![child_node],
346 identity: "Exchange (NoShuffle)".to_string(),
347 })
348 } else {
349 rewrite_stream_node(state, child, true)
350 }
351 } else {
352 match child.get_node_body()? {
353 NodeBody::Exchange(_) => rewrite_stream_node(state, child, false),
355 _ => rewrite_stream_node(state, child, insert_exchange_flag),
357 }
358 }
359 };
360 Ok(StreamNode {
361 input: stream_node
362 .input
363 .into_iter()
364 .map(f)
365 .collect::<Result<_>>()?,
366 ..stream_node
367 })
368}
369
370fn generate_fragment_graph(
372 state: &mut BuildFragmentGraphState,
373 stream_node: StreamNode,
374) -> Result<()> {
375 #[cfg(any())]
379 let stream_node = rewrite_stream_node(state, stream_node, is_stateful_executor(&stream_node))?;
380
381 build_and_add_fragment(state, stream_node)?;
382 Ok(())
383}
384
385fn build_and_add_fragment(
387 state: &mut BuildFragmentGraphState,
388 stream_node: StreamNode,
389) -> Result<Rc<StreamFragment>> {
390 let operator_id = stream_node.operator_id;
391 match state.share_mapping.get(&operator_id) {
392 None => {
393 let mut fragment = state.new_stream_fragment();
394 let node = build_fragment(state, &mut fragment, stream_node)?;
395
396 let operator_id = node.operator_id;
400
401 assert!(fragment.node.is_none());
402 fragment.node = Some(Box::new(node));
403 let fragment_ref = Rc::new(fragment);
404
405 state.fragment_graph.add_fragment(fragment_ref.clone());
406 state
407 .share_mapping
408 .insert(operator_id, fragment_ref.fragment_id);
409 Ok(fragment_ref)
410 }
411 Some(fragment_id) => Ok(state
412 .fragment_graph
413 .get_fragment(fragment_id)
414 .unwrap()
415 .clone()),
416 }
417}
418
419fn build_fragment(
422 state: &mut BuildFragmentGraphState,
423 current_fragment: &mut StreamFragment,
424 mut stream_node: StreamNode,
425) -> Result<StreamNode> {
426 recursive::tracker!().recurse(|_t| {
427 match stream_node.get_node_body()? {
429 NodeBody::BarrierRecv(_) => current_fragment
430 .fragment_type_mask
431 .add(FragmentTypeFlag::BarrierRecv),
432
433 NodeBody::Source(node) => {
434 current_fragment
435 .fragment_type_mask
436 .add(FragmentTypeFlag::Source);
437
438 if let Some(source) = node.source_inner.as_ref()
439 && let Some(source_info) = source.info.as_ref()
440 && ((source_info.is_shared() && !source_info.is_distributed)
441 || source.with_properties.requires_singleton())
442 {
443 current_fragment.requires_singleton = true;
444 }
445 }
446
447 NodeBody::Dml(_) => {
448 current_fragment
449 .fragment_type_mask
450 .add(FragmentTypeFlag::Dml);
451 }
452
453 NodeBody::Materialize(_) => {
454 current_fragment
455 .fragment_type_mask
456 .add(FragmentTypeFlag::Mview);
457 }
458
459 NodeBody::Sink(_) => current_fragment
460 .fragment_type_mask
461 .add(FragmentTypeFlag::Sink),
462
463 NodeBody::TopN(_) => current_fragment.requires_singleton = true,
464
465 NodeBody::StreamScan(node) => {
466 current_fragment
467 .fragment_type_mask
468 .add(FragmentTypeFlag::StreamScan);
469 #[expect(deprecated)]
470 match node.stream_scan_type() {
471 StreamScanType::SnapshotBackfill => {
472 current_fragment
473 .fragment_type_mask
474 .add(FragmentTypeFlag::SnapshotBackfillStreamScan);
475 state.has_snapshot_backfill = true;
476 state.has_any_backfill = true;
477 }
478 StreamScanType::Backfill | StreamScanType::ArrangementBackfill => {
479 state.has_any_backfill = true;
480 }
481 StreamScanType::CrossDbSnapshotBackfill => {
482 current_fragment
483 .fragment_type_mask
484 .add(FragmentTypeFlag::CrossDbSnapshotBackfillStreamScan);
485 state.has_cross_db_snapshot_backfill = true;
486 state.has_any_backfill = true;
487 }
488 StreamScanType::Unspecified
489 | StreamScanType::Chain
490 | StreamScanType::Rearrange
491 | StreamScanType::UpstreamOnly => {}
492 }
493 state.dependent_table_ids.insert(node.table_id);
496 }
497
498 NodeBody::StreamCdcScan(node) => {
499 if let Some(o) = node.options
500 && CdcScanOptions::from_proto(&o).is_parallelized_backfill()
501 {
502 current_fragment
504 .fragment_type_mask
505 .add(FragmentTypeFlag::StreamCdcScan);
506 } else {
507 current_fragment
508 .fragment_type_mask
509 .add(FragmentTypeFlag::StreamScan);
510 current_fragment.requires_singleton = true;
512 }
513 state.has_source_backfill = true;
514 state.has_any_backfill = true;
515 }
516
517 NodeBody::CdcFilter(node) => {
518 current_fragment
519 .fragment_type_mask
520 .add(FragmentTypeFlag::CdcFilter);
521 state
523 .dependent_table_ids
524 .insert(node.upstream_source_id.as_cdc_table_id());
525 }
526 NodeBody::SourceBackfill(node) => {
527 current_fragment
528 .fragment_type_mask
529 .add(FragmentTypeFlag::SourceScan);
530 let source_id = node.upstream_source_id;
532 state
533 .dependent_table_ids
534 .insert(source_id.as_cdc_table_id());
535 state.has_source_backfill = true;
536 state.has_any_backfill = true;
537 }
538
539 NodeBody::Now(_) => {
540 current_fragment
542 .fragment_type_mask
543 .add(FragmentTypeFlag::Now);
544 current_fragment.requires_singleton = true;
545 }
546
547 NodeBody::Values(_) => {
548 current_fragment
549 .fragment_type_mask
550 .add(FragmentTypeFlag::Values);
551 current_fragment.requires_singleton = true;
552 }
553
554 NodeBody::StreamFsFetch(_) => {
555 current_fragment
556 .fragment_type_mask
557 .add(FragmentTypeFlag::FsFetch);
558 }
559
560 NodeBody::VectorIndexWrite(_) => {
561 current_fragment
562 .fragment_type_mask
563 .add(FragmentTypeFlag::VectorIndexWrite);
564 }
565
566 NodeBody::UpstreamSinkUnion(_) => {
567 current_fragment
568 .fragment_type_mask
569 .add(FragmentTypeFlag::UpstreamSinkUnion);
570 }
571
572 NodeBody::LocalityProvider(_) => {
573 current_fragment
574 .fragment_type_mask
575 .add(FragmentTypeFlag::LocalityProvider);
576 }
577
578 _ => {}
579 };
580
581 if let NodeBody::DeltaIndexJoin(delta_index_join) = stream_node.node_body.as_mut().unwrap()
583 {
584 if delta_index_join.get_join_type()? == JoinType::Inner
585 && delta_index_join.condition.is_none()
586 {
587 return build_delta_join_without_arrange(state, current_fragment, stream_node);
588 } else {
589 panic!("only inner join without non-equal condition is supported for delta joins");
590 }
591 }
592
593 if let NodeBody::Exchange(_) = stream_node.node_body.as_ref().unwrap() {
598 stream_node = state.gen_no_op_stream_node(stream_node);
599 }
600
601 stream_node.input = stream_node
603 .input
604 .into_iter()
605 .map(|mut child_node| {
606 match child_node.get_node_body()? {
607 NodeBody::Exchange(_) if child_node.input.is_empty() => Ok(child_node),
610 NodeBody::Exchange(exchange_node) => {
612 let exchange_node_strategy = exchange_node.get_strategy()?.clone();
613
614 let [input]: [_; 1] =
616 std::mem::take(&mut child_node.input).try_into().unwrap();
617 let child_fragment = build_and_add_fragment(state, input)?;
618
619 let result = state.fragment_graph.try_add_edge(
620 child_fragment.fragment_id,
621 current_fragment.fragment_id,
622 StreamFragmentEdge {
623 dispatch_strategy: exchange_node_strategy.clone(),
624 link_id: child_node.operator_id.as_raw_id(),
626 },
627 );
628
629 if result.is_err() {
633 child_node.operator_id = state.gen_operator_id();
636
637 let ref_fragment_node = child_fragment.node.as_ref().unwrap();
639 let no_shuffle_strategy = DispatchStrategy {
640 r#type: DispatcherType::NoShuffle as i32,
641 dist_key_indices: vec![],
642 output_mapping: PbDispatchOutputMapping::identical(
643 ref_fragment_node.fields.len(),
644 )
645 .into(),
646 };
647
648 let no_shuffle_exchange_operator_id = state.gen_operator_id();
649
650 let no_op_fragment = {
651 let node = state.gen_no_op_stream_node(StreamNode {
652 operator_id: no_shuffle_exchange_operator_id,
653 identity: "StreamNoShuffleExchange".into(),
654 node_body: Some(NodeBody::Exchange(Box::new(ExchangeNode {
655 strategy: Some(no_shuffle_strategy.clone()),
656 }))),
657 input: vec![],
658
659 stream_key: ref_fragment_node.stream_key.clone(),
661 stream_kind: ref_fragment_node.stream_kind,
662 fields: ref_fragment_node.fields.clone(),
663 });
664
665 let mut fragment = state.new_stream_fragment();
666 fragment.node = Some(node.into());
667 Rc::new(fragment)
668 };
669
670 state.fragment_graph.add_fragment(no_op_fragment.clone());
671
672 state.fragment_graph.add_edge(
673 child_fragment.fragment_id,
674 no_op_fragment.fragment_id,
675 StreamFragmentEdge {
676 dispatch_strategy: no_shuffle_strategy,
678 link_id: no_shuffle_exchange_operator_id.as_raw_id(),
679 },
680 );
681 state.fragment_graph.add_edge(
682 no_op_fragment.fragment_id,
683 current_fragment.fragment_id,
684 StreamFragmentEdge {
685 dispatch_strategy: exchange_node_strategy,
687 link_id: child_node.operator_id.as_raw_id(),
688 },
689 );
690 }
691
692 Ok(child_node)
693 }
694
695 _ => build_fragment(state, current_fragment, child_node),
697 }
698 })
699 .collect::<Result<_>>()?;
700 Ok(stream_node)
701 })
702}
703
704#[cfg(test)]
705mod tests {
706 use risingwave_common::types::StructType;
707 use risingwave_pb::catalog::PbTable;
708 use risingwave_pb::common::PbColumnOrder;
709 use risingwave_pb::plan_common::{PbColumnCatalog, PbColumnDesc};
710 use risingwave_pb::stream_plan::TopNNode;
711
712 use super::*;
713
714 fn top_n_with_pk_column(data_type: DataType) -> StreamNode {
716 let table = PbTable {
717 columns: vec![PbColumnCatalog {
718 column_desc: Some(PbColumnDesc {
719 name: "v".to_owned(),
720 column_type: Some(data_type.to_protobuf()),
721 ..Default::default()
722 }),
723 ..Default::default()
724 }],
725 pk: vec![PbColumnOrder {
726 column_index: 0,
727 order_type: None,
728 }],
729 ..Default::default()
730 };
731 StreamNode {
732 node_body: Some(NodeBody::TopN(Box::new(TopNNode {
733 table: Some(table),
734 ..Default::default()
735 }))),
736 ..Default::default()
737 }
738 }
739
740 #[test]
741 fn rejects_variant_in_internal_state_table_pk() {
742 for data_type in [
743 DataType::Variant,
744 DataType::list(DataType::Variant),
745 DataType::Struct(StructType::new(vec![("v", DataType::Variant)])),
746 ] {
747 let mut node = top_n_with_pk_column(data_type.clone());
748 let err = reject_variant_in_internal_storage_key(&mut node).unwrap_err();
749 assert!(
750 err.to_string().contains("internal state table of `TopN`"),
751 "{data_type:?}: {err}"
752 );
753 }
754
755 let mut node = top_n_with_pk_column(DataType::Jsonb);
756 reject_variant_in_internal_storage_key(&mut node).unwrap();
757 }
758}