Skip to main content

risingwave_frontend/scheduler/
plan_fragmenter.rs

1// Copyright 2022 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::cmp::min;
16use std::collections::{HashMap, HashSet};
17use std::fmt::{Debug, Display, Formatter};
18use std::num::NonZeroU64;
19
20use anyhow::anyhow;
21use async_recursion::async_recursion;
22use enum_as_inner::EnumAsInner;
23use futures::TryStreamExt;
24use itertools::Itertools;
25use petgraph::{Directed, Graph};
26use pgwire::pg_server::SessionId;
27use risingwave_batch::error::BatchError;
28use risingwave_batch::worker_manager::worker_node_manager::WorkerNodeSelector;
29use risingwave_common::bitmap::{Bitmap, BitmapBuilder};
30use risingwave_common::catalog::Schema;
31use risingwave_common::hash::table_distribution::TableDistribution;
32use risingwave_common::hash::{WorkerSlotId, WorkerSlotMapping};
33use risingwave_common::util::scan_range::ScanRange;
34use risingwave_connector::source::filesystem::opendal_source::opendal_enumerator::OpendalEnumerator;
35use risingwave_connector::source::filesystem::opendal_source::{
36    BatchPosixFsEnumerator, OpendalAzblob, OpendalGcs, OpendalS3,
37};
38use risingwave_connector::source::iceberg::{IcebergFileScanTask, IcebergScanTaskPlanner};
39use risingwave_connector::source::kafka::KafkaSplitEnumerator;
40use risingwave_connector::source::prelude::DatagenSplitEnumerator;
41use risingwave_connector::source::reader::reader::build_opendal_fs_list_for_batch;
42use risingwave_connector::source::{
43    ConnectorProperties, SourceEnumeratorContext, SplitEnumerator, SplitImpl,
44};
45use risingwave_pb::batch_plan::plan_node::NodeBody;
46use risingwave_pb::batch_plan::{ExchangeInfo, ScanRange as ScanRangeProto};
47use risingwave_pb::plan_common::Field as PbField;
48use serde::ser::SerializeStruct;
49use serde::{Serialize, Serializer};
50use uuid::Uuid;
51
52use super::SchedulerError;
53use crate::TableCatalog;
54use crate::catalog::TableId;
55use crate::catalog::catalog_service::CatalogReader;
56use crate::optimizer::plan_node::generic::{GenericPlanRef, PhysicalPlanRef};
57use crate::optimizer::plan_node::{
58    BatchIcebergScan, BatchKafkaScan, BatchPlanNodeType, BatchPlanRef as PlanRef, BatchSource,
59    PlanNodeId,
60};
61use crate::optimizer::property::Distribution;
62use crate::scheduler::SchedulerResult;
63
64#[derive(Clone, Debug, Hash, Eq, PartialEq)]
65pub struct QueryId {
66    pub id: String,
67}
68
69impl std::fmt::Display for QueryId {
70    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
71        write!(f, "QueryId:{}", self.id)
72    }
73}
74
75#[derive(Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
76pub struct StageId(u32);
77
78impl Display for StageId {
79    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
80        write!(f, "{}", self.0)
81    }
82}
83
84impl Debug for StageId {
85    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
86        write!(f, "{:?}", self.0)
87    }
88}
89
90impl From<StageId> for u32 {
91    fn from(value: StageId) -> Self {
92        value.0
93    }
94}
95
96impl From<u32> for StageId {
97    fn from(value: u32) -> Self {
98        StageId(value)
99    }
100}
101
102impl Serialize for StageId {
103    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
104    where
105        S: Serializer,
106    {
107        self.0.serialize(serializer)
108    }
109}
110
111impl StageId {
112    pub fn inc(&mut self) {
113        self.0 += 1;
114    }
115}
116
117// Root stage always has only one task.
118pub const ROOT_TASK_ID: u64 = 0;
119// Root task has only one output.
120pub const ROOT_TASK_OUTPUT_ID: u64 = 0;
121pub type TaskId = u64;
122
123/// Generated by [`BatchPlanFragmenter`] and used in query execution graph.
124#[derive(Debug)]
125#[cfg_attr(test, derive(Clone))]
126pub struct ExecutionPlanNode {
127    pub plan_node_id: PlanNodeId,
128    pub plan_node_type: BatchPlanNodeType,
129    pub node: NodeBody,
130    pub schema: Vec<PbField>,
131
132    pub children: Vec<ExecutionPlanNode>,
133
134    /// The stage id of the source of `BatchExchange`.
135    /// Used to find `ExchangeSource` from scheduler when creating `PlanNode`.
136    ///
137    /// `None` when this node is not `BatchExchange`.
138    pub source_stage_id: Option<StageId>,
139}
140
141impl Serialize for ExecutionPlanNode {
142    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
143    where
144        S: serde::Serializer,
145    {
146        let mut state = serializer.serialize_struct("QueryStage", 5)?;
147        state.serialize_field("plan_node_id", &self.plan_node_id)?;
148        state.serialize_field("plan_node_type", &self.plan_node_type)?;
149        state.serialize_field("schema", &self.schema)?;
150        state.serialize_field("children", &self.children)?;
151        state.serialize_field("source_stage_id", &self.source_stage_id)?;
152        state.end()
153    }
154}
155
156impl TryFrom<PlanRef> for ExecutionPlanNode {
157    type Error = SchedulerError;
158
159    fn try_from(plan_node: PlanRef) -> Result<Self, Self::Error> {
160        Ok(Self {
161            plan_node_id: plan_node.plan_base().id(),
162            plan_node_type: plan_node.node_type(),
163            node: plan_node.try_to_batch_prost_body()?,
164            children: vec![],
165            schema: plan_node.schema().to_prost(),
166            source_stage_id: None,
167        })
168    }
169}
170
171impl ExecutionPlanNode {
172    pub fn node_type(&self) -> BatchPlanNodeType {
173        self.plan_node_type
174    }
175}
176
177/// `BatchPlanFragmenter` splits a query plan into fragments.
178pub struct BatchPlanFragmenter {
179    query_id: QueryId,
180    next_stage_id: StageId,
181    worker_node_manager: WorkerNodeSelector,
182    catalog_reader: CatalogReader,
183
184    batch_parallelism: usize,
185
186    stage_graph_builder: Option<StageGraphBuilder>,
187    stage_graph: Option<StageGraph>,
188}
189
190impl Default for QueryId {
191    fn default() -> Self {
192        Self {
193            id: Uuid::new_v4().to_string(),
194        }
195    }
196}
197
198impl BatchPlanFragmenter {
199    pub fn new(
200        worker_node_manager: WorkerNodeSelector,
201        catalog_reader: CatalogReader,
202        batch_parallelism: Option<NonZeroU64>,
203        batch_node: PlanRef,
204    ) -> SchedulerResult<Self> {
205        // if batch_parallelism is None, it means no limit, we will use the available nodes count as
206        // parallelism.
207        // if batch_parallelism is Some(num), we will use the min(num, the available
208        // nodes count) as parallelism.
209        let batch_parallelism = if let Some(num) = batch_parallelism {
210            // can be 0 if no available serving worker
211            min(
212                num.get() as usize,
213                worker_node_manager.schedule_unit_count(),
214            )
215        } else {
216            // can be 0 if no available serving worker
217            worker_node_manager.schedule_unit_count()
218        };
219
220        let mut plan_fragmenter = Self {
221            query_id: Default::default(),
222            next_stage_id: 0.into(),
223            worker_node_manager,
224            catalog_reader,
225            batch_parallelism,
226            stage_graph_builder: Some(StageGraphBuilder::new(batch_parallelism)),
227            stage_graph: None,
228        };
229        plan_fragmenter.split_into_stage(batch_node)?;
230        Ok(plan_fragmenter)
231    }
232
233    /// Split the plan node into each stages, based on exchange node.
234    fn split_into_stage(&mut self, batch_node: PlanRef) -> SchedulerResult<()> {
235        let root_stage_id = self.new_stage(
236            batch_node,
237            Some(Distribution::Single.to_prost(
238                1,
239                &self.catalog_reader,
240                &self.worker_node_manager,
241                self.batch_parallelism,
242            )?),
243        )?;
244        self.stage_graph = Some(
245            self.stage_graph_builder
246                .take()
247                .unwrap()
248                .build(root_stage_id),
249        );
250        Ok(())
251    }
252}
253
254/// The fragmented query generated by [`BatchPlanFragmenter`].
255#[derive(Debug)]
256#[cfg_attr(test, derive(Clone))]
257pub struct Query {
258    /// Query id should always be unique.
259    pub query_id: QueryId,
260    pub stage_graph: StageGraph,
261}
262
263impl Query {
264    pub fn leaf_stages(&self) -> Vec<StageId> {
265        let mut ret_leaf_stages = Vec::new();
266        for stage_id in self.stage_graph.stages.keys() {
267            if self
268                .stage_graph
269                .get_child_stages_unchecked(stage_id)
270                .is_empty()
271            {
272                ret_leaf_stages.push(*stage_id);
273            }
274        }
275        ret_leaf_stages
276    }
277
278    pub fn get_parents(&self, stage_id: &StageId) -> &HashSet<StageId> {
279        self.stage_graph.parent_edges.get(stage_id).unwrap()
280    }
281
282    pub fn root_stage_id(&self) -> StageId {
283        self.stage_graph.root_stage_id
284    }
285
286    pub fn query_id(&self) -> &QueryId {
287        &self.query_id
288    }
289
290    pub fn stages_with_table_scan(&self) -> HashSet<StageId> {
291        self.stage_graph
292            .stages
293            .iter()
294            .filter_map(|(stage_id, stage_query)| {
295                if stage_query.has_table_scan() {
296                    Some(*stage_id)
297                } else {
298                    None
299                }
300            })
301            .collect()
302    }
303
304    pub fn has_lookup_join_stage(&self) -> bool {
305        self.stage_graph
306            .stages
307            .iter()
308            .any(|(_stage_id, stage_query)| stage_query.has_lookup_join())
309    }
310
311    pub fn stage(&self, stage_id: StageId) -> &QueryStage {
312        &self.stage_graph.stages[&stage_id]
313    }
314
315    pub fn batch_parallelism(&self) -> usize {
316        self.stage_graph.batch_parallelism
317    }
318}
319
320#[derive(Debug, Clone)]
321pub enum SourceFetchParameters {
322    KafkaTimebound {
323        lower: Option<i64>,
324        upper: Option<i64>,
325    },
326    Empty,
327}
328
329#[derive(Debug, Clone)]
330pub enum UnpartitionedData {
331    Iceberg {
332        task: IcebergFileScanTask,
333        limit: Option<u64>,
334    },
335}
336
337#[derive(Debug, Clone)]
338pub struct SourceFetchInfo {
339    pub schema: Schema,
340    /// These are user-configured connector properties.
341    /// e.g. host, username, etc...
342    pub connector: ConnectorProperties,
343    /// These parameters are internally derived by the plan node.
344    /// e.g. predicate pushdown for iceberg, timebound for kafka.
345    pub fetch_parameters: SourceFetchParameters,
346}
347
348#[derive(Clone)]
349pub enum SourceScanInfo {
350    /// Split Info
351    Incomplete(SourceFetchInfo),
352    Unpartitioned(UnpartitionedData),
353    Complete(Vec<SplitImpl>),
354}
355
356impl SourceScanInfo {
357    pub fn new(fetch_info: SourceFetchInfo) -> Self {
358        Self::Incomplete(fetch_info)
359    }
360
361    pub async fn complete(self, batch_parallelism: usize) -> SchedulerResult<Self> {
362        match self {
363            SourceScanInfo::Incomplete(fetch_info) => fetch_info.complete(batch_parallelism).await,
364            SourceScanInfo::Unpartitioned(data) => data.complete(batch_parallelism),
365            SourceScanInfo::Complete(_) => {
366                unreachable!("Never call complete when SourceScanInfo is already complete")
367            }
368        }
369    }
370
371    pub fn split_info(&self) -> SchedulerResult<&Vec<SplitImpl>> {
372        match self {
373            Self::Incomplete(_) => Err(SchedulerError::Internal(anyhow!(
374                "Should not get split info from incomplete source scan info"
375            ))),
376            Self::Unpartitioned(_) => Err(SchedulerError::Internal(anyhow!(
377                "Should not get split info from unpartitioned source scan info"
378            ))),
379            Self::Complete(split_info) => Ok(split_info),
380        }
381    }
382}
383
384impl UnpartitionedData {
385    fn complete(self, batch_parallelism: usize) -> SchedulerResult<SourceScanInfo> {
386        let splits = match self {
387            UnpartitionedData::Iceberg { task, limit } => {
388                IcebergScanTaskPlanner::plan_splits(task, batch_parallelism, limit)?
389                    .into_iter()
390                    .map(SplitImpl::Iceberg)
391                    .collect()
392            }
393        };
394        Ok(SourceScanInfo::Complete(splits))
395    }
396}
397
398impl SourceFetchInfo {
399    async fn complete(self, _batch_parallelism: usize) -> SchedulerResult<SourceScanInfo> {
400        match (self.connector, self.fetch_parameters) {
401            (
402                ConnectorProperties::Kafka(prop),
403                SourceFetchParameters::KafkaTimebound { lower, upper },
404            ) => {
405                let mut kafka_enumerator =
406                    KafkaSplitEnumerator::new(*prop, SourceEnumeratorContext::dummy().into())
407                        .await?;
408                let split_info = kafka_enumerator
409                    .list_splits_batch(lower, upper)
410                    .await?
411                    .into_iter()
412                    .map(SplitImpl::Kafka)
413                    .collect_vec();
414
415                Ok(SourceScanInfo::Complete(split_info))
416            }
417            (ConnectorProperties::Datagen(prop), SourceFetchParameters::Empty) => {
418                let mut datagen_enumerator =
419                    DatagenSplitEnumerator::new(*prop, SourceEnumeratorContext::dummy().into())
420                        .await?;
421                let split_info = datagen_enumerator.list_splits().await?;
422                let res = split_info.into_iter().map(SplitImpl::Datagen).collect_vec();
423
424                Ok(SourceScanInfo::Complete(res))
425            }
426            (ConnectorProperties::OpendalS3(prop), SourceFetchParameters::Empty) => {
427                let lister: OpendalEnumerator<OpendalS3> = OpendalEnumerator::new_s3_source(
428                    &prop.s3_properties,
429                    prop.assume_role,
430                    prop.fs_common.compression_format,
431                )?;
432                let stream = build_opendal_fs_list_for_batch(lister);
433
434                let batch_res: Vec<_> = stream.try_collect().await?;
435                let res = batch_res
436                    .into_iter()
437                    .map(SplitImpl::OpendalS3)
438                    .collect_vec();
439
440                Ok(SourceScanInfo::Complete(res))
441            }
442            (ConnectorProperties::Gcs(prop), SourceFetchParameters::Empty) => {
443                let lister: OpendalEnumerator<OpendalGcs> =
444                    OpendalEnumerator::new_gcs_source(*prop)?;
445                let stream = build_opendal_fs_list_for_batch(lister);
446                let batch_res: Vec<_> = stream.try_collect().await?;
447                let res = batch_res.into_iter().map(SplitImpl::Gcs).collect_vec();
448
449                Ok(SourceScanInfo::Complete(res))
450            }
451            (ConnectorProperties::Azblob(prop), SourceFetchParameters::Empty) => {
452                let lister: OpendalEnumerator<OpendalAzblob> =
453                    OpendalEnumerator::new_azblob_source(*prop)?;
454                let stream = build_opendal_fs_list_for_batch(lister);
455                let batch_res: Vec<_> = stream.try_collect().await?;
456                let res = batch_res.into_iter().map(SplitImpl::Azblob).collect_vec();
457
458                Ok(SourceScanInfo::Complete(res))
459            }
460            (ConnectorProperties::BatchPosixFs(prop), SourceFetchParameters::Empty) => {
461                use risingwave_connector::source::SplitEnumerator;
462                let mut enumerator = BatchPosixFsEnumerator::new(
463                    *prop,
464                    risingwave_connector::source::SourceEnumeratorContext::dummy().into(),
465                )
466                .await?;
467                let splits = enumerator.list_splits().await?;
468                let res = splits
469                    .into_iter()
470                    .map(SplitImpl::BatchPosixFs)
471                    .collect_vec();
472
473                Ok(SourceScanInfo::Complete(res))
474            }
475            (connector, _) => Err(SchedulerError::Internal(anyhow!(
476                "Unsupported to query directly from this {} source, \
477                 please create a table or streaming job from it",
478                connector.kind()
479            ))),
480        }
481    }
482}
483
484#[derive(Clone, Debug)]
485pub struct TableScanInfo {
486    /// The name of the table to scan.
487    name: String,
488
489    /// Indicates the table partitions to be read by scan tasks. Unnecessary partitions are already
490    /// pruned.
491    ///
492    /// For singleton table, this field is still `Some` and only contains a single partition with
493    /// full vnode bitmap, since we need to know where to schedule the singleton scan task.
494    ///
495    /// `None` iff the table is a system table.
496    partitions: Option<HashMap<WorkerSlotId, TablePartitionInfo>>,
497}
498
499impl TableScanInfo {
500    /// For normal tables, `partitions` should always be `Some`.
501    pub fn new(name: String, partitions: HashMap<WorkerSlotId, TablePartitionInfo>) -> Self {
502        Self {
503            name,
504            partitions: Some(partitions),
505        }
506    }
507
508    /// For system table, there's no partition info.
509    pub fn system_table(name: String) -> Self {
510        Self {
511            name,
512            partitions: None,
513        }
514    }
515
516    pub fn name(&self) -> &str {
517        self.name.as_ref()
518    }
519
520    pub fn partitions(&self) -> Option<&HashMap<WorkerSlotId, TablePartitionInfo>> {
521        self.partitions.as_ref()
522    }
523}
524
525#[derive(Clone, Debug)]
526pub struct TablePartitionInfo {
527    pub vnode_bitmap: Bitmap,
528    pub scan_ranges: Vec<ScanRangeProto>,
529}
530
531#[derive(Clone, Debug, EnumAsInner)]
532pub enum PartitionInfo {
533    Table(TablePartitionInfo),
534    Source(Vec<SplitImpl>),
535    File(Vec<String>),
536}
537
538#[derive(Clone, Debug)]
539pub struct FileScanInfo {
540    pub file_location: Vec<String>,
541}
542
543/// Fragment part of `Query`.
544#[cfg_attr(test, derive(Clone))]
545pub struct QueryStage {
546    pub id: StageId,
547    pub root: ExecutionPlanNode,
548    pub exchange_info: Option<ExchangeInfo>,
549    pub parallelism: Option<u32>,
550    /// Indicates whether this stage contains a table scan node and the table's information if so.
551    pub table_scan_info: Option<TableScanInfo>,
552    pub source_info: Option<SourceScanInfo>,
553    pub file_scan_info: Option<FileScanInfo>,
554    pub has_lookup_join: bool,
555    pub dml_table_id: Option<TableId>,
556    pub session_id: SessionId,
557    pub batch_enable_distributed_dml: bool,
558
559    /// Used to generate exchange information when complete source scan information.
560    children_exchange_distribution: Option<HashMap<StageId, Distribution>>,
561}
562
563impl QueryStage {
564    /// If true, this stage contains table scan executor that creates
565    /// Hummock iterators to read data from table. The iterator is initialized during
566    /// the executor building process on the batch execution engine.
567    pub fn has_table_scan(&self) -> bool {
568        self.table_scan_info.is_some()
569    }
570
571    /// If true, this stage contains lookup join executor.
572    /// We need to delay epoch unpin util the end of the query.
573    pub fn has_lookup_join(&self) -> bool {
574        self.has_lookup_join
575    }
576
577    pub fn with_exchange_info(
578        self,
579        exchange_info: Option<ExchangeInfo>,
580        parallelism: Option<u32>,
581    ) -> Self {
582        if let Some(exchange_info) = exchange_info {
583            Self {
584                id: self.id,
585                root: self.root,
586                exchange_info: Some(exchange_info),
587                parallelism,
588                table_scan_info: self.table_scan_info,
589                source_info: self.source_info,
590                file_scan_info: self.file_scan_info,
591                has_lookup_join: self.has_lookup_join,
592                dml_table_id: self.dml_table_id,
593                session_id: self.session_id,
594                batch_enable_distributed_dml: self.batch_enable_distributed_dml,
595                children_exchange_distribution: self.children_exchange_distribution,
596            }
597        } else {
598            self
599        }
600    }
601
602    pub fn with_exchange_info_and_complete_source_info(
603        self,
604        exchange_info: Option<ExchangeInfo>,
605        source_info: SourceScanInfo,
606        task_parallelism: u32,
607    ) -> Self {
608        assert!(matches!(source_info, SourceScanInfo::Complete(_)));
609        let exchange_info = if let Some(exchange_info) = exchange_info {
610            Some(exchange_info)
611        } else {
612            self.exchange_info
613        };
614        Self {
615            id: self.id,
616            root: self.root,
617            exchange_info,
618            parallelism: Some(task_parallelism),
619            table_scan_info: self.table_scan_info,
620            source_info: Some(source_info),
621            file_scan_info: self.file_scan_info,
622            has_lookup_join: self.has_lookup_join,
623            dml_table_id: self.dml_table_id,
624            session_id: self.session_id,
625            batch_enable_distributed_dml: self.batch_enable_distributed_dml,
626            children_exchange_distribution: None,
627        }
628    }
629}
630
631impl Debug for QueryStage {
632    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
633        f.debug_struct("QueryStage")
634            .field("id", &self.id)
635            .field("parallelism", &self.parallelism)
636            .field("exchange_info", &self.exchange_info)
637            .field("has_table_scan", &self.has_table_scan())
638            .finish()
639    }
640}
641
642impl Serialize for QueryStage {
643    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
644    where
645        S: serde::Serializer,
646    {
647        let mut state = serializer.serialize_struct("QueryStage", 3)?;
648        state.serialize_field("root", &self.root)?;
649        state.serialize_field("parallelism", &self.parallelism)?;
650        state.serialize_field("exchange_info", &self.exchange_info)?;
651        state.end()
652    }
653}
654
655struct QueryStageBuilder {
656    id: StageId,
657    root: Option<ExecutionPlanNode>,
658    parallelism: Option<u32>,
659    exchange_info: Option<ExchangeInfo>,
660
661    children_stages: Vec<StageId>,
662    /// See also [`QueryStage::table_scan_info`].
663    table_scan_info: Option<TableScanInfo>,
664    source_info: Option<SourceScanInfo>,
665    file_scan_file: Option<FileScanInfo>,
666    has_lookup_join: bool,
667    dml_table_id: Option<TableId>,
668    session_id: SessionId,
669    batch_enable_distributed_dml: bool,
670
671    children_exchange_distribution: HashMap<StageId, Distribution>,
672}
673
674impl QueryStageBuilder {
675    fn new(
676        id: StageId,
677        parallelism: Option<u32>,
678        exchange_info: Option<ExchangeInfo>,
679        table_scan_info: Option<TableScanInfo>,
680        source_info: Option<SourceScanInfo>,
681        file_scan_file: Option<FileScanInfo>,
682        has_lookup_join: bool,
683        dml_table_id: Option<TableId>,
684        session_id: SessionId,
685        batch_enable_distributed_dml: bool,
686    ) -> Self {
687        Self {
688            id,
689            root: None,
690            parallelism,
691            exchange_info,
692            children_stages: vec![],
693            table_scan_info,
694            source_info,
695            file_scan_file,
696            has_lookup_join,
697            dml_table_id,
698            session_id,
699            batch_enable_distributed_dml,
700            children_exchange_distribution: HashMap::new(),
701        }
702    }
703
704    fn finish(self, stage_graph_builder: &mut StageGraphBuilder) -> StageId {
705        let children_exchange_distribution = if self.parallelism.is_none() {
706            Some(self.children_exchange_distribution)
707        } else {
708            None
709        };
710        let stage = QueryStage {
711            id: self.id,
712            root: self.root.unwrap(),
713            exchange_info: self.exchange_info,
714            parallelism: self.parallelism,
715            table_scan_info: self.table_scan_info,
716            source_info: self.source_info,
717            file_scan_info: self.file_scan_file,
718            has_lookup_join: self.has_lookup_join,
719            dml_table_id: self.dml_table_id,
720            session_id: self.session_id,
721            batch_enable_distributed_dml: self.batch_enable_distributed_dml,
722            children_exchange_distribution,
723        };
724
725        let stage_id = stage.id;
726        stage_graph_builder.add_node(stage);
727        for child_stage_id in self.children_stages {
728            stage_graph_builder.link_to_child(self.id, child_stage_id);
729        }
730        stage_id
731    }
732}
733
734/// Maintains how each stage are connected.
735#[derive(Debug, Serialize)]
736#[cfg_attr(test, derive(Clone))]
737pub struct StageGraph {
738    pub root_stage_id: StageId,
739    pub stages: HashMap<StageId, QueryStage>,
740    /// Traverse from top to down. Used in split plan into stages.
741    child_edges: HashMap<StageId, HashSet<StageId>>,
742    /// Traverse from down to top. Used in schedule each stage.
743    parent_edges: HashMap<StageId, HashSet<StageId>>,
744
745    batch_parallelism: usize,
746}
747
748enum StageCompleteInfo {
749    ExchangeInfo((Option<ExchangeInfo>, Option<u32>)),
750    ExchangeWithSourceInfo((Option<ExchangeInfo>, SourceScanInfo, u32)),
751}
752
753impl StageGraph {
754    pub fn get_child_stages_unchecked(&self, stage_id: &StageId) -> &HashSet<StageId> {
755        self.child_edges.get(stage_id).unwrap()
756    }
757
758    pub fn get_child_stages(&self, stage_id: &StageId) -> Option<&HashSet<StageId>> {
759        self.child_edges.get(stage_id)
760    }
761
762    /// Returns stage ids in topology order, s.t. child stage always appears before its parent.
763    pub fn stage_ids_by_topo_order(&self) -> impl Iterator<Item = StageId> {
764        let mut stack = Vec::with_capacity(self.stages.len());
765        stack.push(self.root_stage_id);
766        let mut ret = Vec::with_capacity(self.stages.len());
767        let mut existing = HashSet::with_capacity(self.stages.len());
768
769        while let Some(s) = stack.pop() {
770            if !existing.contains(&s) {
771                ret.push(s);
772                existing.insert(s);
773                stack.extend(&self.child_edges[&s]);
774            }
775        }
776
777        ret.into_iter().rev()
778    }
779
780    async fn complete(
781        self,
782        catalog_reader: &CatalogReader,
783        worker_node_manager: &WorkerNodeSelector,
784    ) -> SchedulerResult<StageGraph> {
785        let mut complete_stages = HashMap::new();
786        self.complete_stage(
787            self.root_stage_id,
788            None,
789            &mut complete_stages,
790            catalog_reader,
791            worker_node_manager,
792        )
793        .await?;
794        let mut stages = self.stages;
795        Ok(StageGraph {
796            root_stage_id: self.root_stage_id,
797            stages: complete_stages
798                .into_iter()
799                .map(|(stage_id, info)| {
800                    let stage = stages.remove(&stage_id).expect("should exist");
801                    let stage = match info {
802                        StageCompleteInfo::ExchangeInfo((exchange_info, parallelism)) => {
803                            stage.with_exchange_info(exchange_info, parallelism)
804                        }
805                        StageCompleteInfo::ExchangeWithSourceInfo((
806                            exchange_info,
807                            source_info,
808                            parallelism,
809                        )) => stage.with_exchange_info_and_complete_source_info(
810                            exchange_info,
811                            source_info,
812                            parallelism,
813                        ),
814                    };
815                    (stage_id, stage)
816                })
817                .collect(),
818            child_edges: self.child_edges,
819            parent_edges: self.parent_edges,
820            batch_parallelism: self.batch_parallelism,
821        })
822    }
823
824    #[async_recursion]
825    async fn complete_stage(
826        &self,
827        stage_id: StageId,
828        exchange_info: Option<ExchangeInfo>,
829        complete_stages: &mut HashMap<StageId, StageCompleteInfo>,
830        catalog_reader: &CatalogReader,
831        worker_node_manager: &WorkerNodeSelector,
832    ) -> SchedulerResult<()> {
833        let stage = &self.stages[&stage_id];
834        let parallelism = if stage.parallelism.is_some() {
835            // If the stage has parallelism, it means it's a complete stage.
836            complete_stages.insert(
837                stage.id,
838                StageCompleteInfo::ExchangeInfo((exchange_info, stage.parallelism)),
839            );
840            None
841        } else if matches!(
842            stage.source_info,
843            Some(SourceScanInfo::Incomplete(_)) | Some(SourceScanInfo::Unpartitioned(_))
844        ) {
845            let complete_source_info = stage
846                .source_info
847                .as_ref()
848                .unwrap()
849                .clone()
850                .complete(self.batch_parallelism)
851                .await?;
852
853            // For batch reading file source, the number of files involved is typically large.
854            // In order to avoid generating a task for each file, the parallelism of tasks is limited here.
855            // The minimum `task_parallelism` is 1. Additionally, `task_parallelism`
856            // must be greater than the number of files to read. Therefore, we first take the
857            // minimum of the number of files and (self.batch_parallelism / 2). If the number of
858            // files is 0, we set task_parallelism to 1.
859
860            let task_parallelism = match &stage.source_info {
861                Some(SourceScanInfo::Incomplete(source_fetch_info)) => {
862                    match source_fetch_info.connector {
863                        ConnectorProperties::Gcs(_)
864                        | ConnectorProperties::OpendalS3(_)
865                        | ConnectorProperties::Azblob(_) => (min(
866                            complete_source_info.split_info().unwrap().len() as u32,
867                            (self.batch_parallelism / 2) as u32,
868                        ))
869                        .max(1),
870                        _ => complete_source_info.split_info().unwrap().len() as u32,
871                    }
872                }
873                _ => complete_source_info.split_info().unwrap().len() as u32,
874            };
875            // For file source batch read, all the files  to be read are divide into several parts to prevent the task from taking up too many resources.
876            // todo(wcy-fdu): Currently it will be divided into half of batch_parallelism groups, and this will be changed to configurable later.
877            let complete_stage_info = StageCompleteInfo::ExchangeWithSourceInfo((
878                exchange_info,
879                complete_source_info,
880                task_parallelism,
881            ));
882            complete_stages.insert(stage.id, complete_stage_info);
883            Some(task_parallelism)
884        } else {
885            assert!(stage.file_scan_info.is_some());
886            let parallelism = min(
887                self.batch_parallelism / 2,
888                stage.file_scan_info.as_ref().unwrap().file_location.len(),
889            );
890            complete_stages.insert(
891                stage.id,
892                StageCompleteInfo::ExchangeInfo((exchange_info, Some(parallelism as u32))),
893            );
894            None
895        };
896
897        for child_stage_id in self
898            .child_edges
899            .get(&stage.id)
900            .map(|edges| edges.iter())
901            .into_iter()
902            .flatten()
903        {
904            let exchange_info = if let Some(parallelism) = parallelism {
905                let exchange_distribution = stage
906                    .children_exchange_distribution
907                    .as_ref()
908                    .unwrap()
909                    .get(child_stage_id)
910                    .expect("Exchange distribution is not consistent with the stage graph");
911                Some(exchange_distribution.to_prost(
912                    parallelism,
913                    catalog_reader,
914                    worker_node_manager,
915                    self.batch_parallelism,
916                )?)
917            } else {
918                None
919            };
920            self.complete_stage(
921                *child_stage_id,
922                exchange_info,
923                complete_stages,
924                catalog_reader,
925                worker_node_manager,
926            )
927            .await?;
928        }
929
930        Ok(())
931    }
932
933    /// Converts the `StageGraph` into a `petgraph::graph::Graph<String, String>`.
934    pub fn to_petgraph(&self) -> Graph<String, String, Directed> {
935        let mut graph = Graph::<String, String, Directed>::new();
936
937        let mut node_indices = HashMap::new();
938
939        // Add all stages as nodes
940        for (&stage_id, stage_ref) in self.stages.iter().sorted_by_key(|(id, _)| **id) {
941            let node_label = format!("Stage {}: {:?}", stage_id, stage_ref);
942            let node_index = graph.add_node(node_label);
943            node_indices.insert(stage_id, node_index);
944        }
945
946        // Add edges between stages based on child_edges
947        for (&parent_id, children) in &self.child_edges {
948            if let Some(&parent_index) = node_indices.get(&parent_id) {
949                for &child_id in children {
950                    if let Some(&child_index) = node_indices.get(&child_id) {
951                        // Add an edge from parent to child
952                        graph.add_edge(parent_index, child_index, "".to_owned());
953                    }
954                }
955            }
956        }
957
958        graph
959    }
960}
961
962struct StageGraphBuilder {
963    stages: HashMap<StageId, QueryStage>,
964    child_edges: HashMap<StageId, HashSet<StageId>>,
965    parent_edges: HashMap<StageId, HashSet<StageId>>,
966    batch_parallelism: usize,
967}
968
969impl StageGraphBuilder {
970    pub fn new(batch_parallelism: usize) -> Self {
971        Self {
972            stages: HashMap::new(),
973            child_edges: HashMap::new(),
974            parent_edges: HashMap::new(),
975            batch_parallelism,
976        }
977    }
978
979    pub fn build(self, root_stage_id: StageId) -> StageGraph {
980        StageGraph {
981            root_stage_id,
982            stages: self.stages,
983            child_edges: self.child_edges,
984            parent_edges: self.parent_edges,
985            batch_parallelism: self.batch_parallelism,
986        }
987    }
988
989    /// Link parent stage and child stage. Maintain the mappings of parent -> child and child ->
990    /// parent.
991    pub fn link_to_child(&mut self, parent_id: StageId, child_id: StageId) {
992        self.child_edges
993            .get_mut(&parent_id)
994            .unwrap()
995            .insert(child_id);
996        self.parent_edges
997            .get_mut(&child_id)
998            .unwrap()
999            .insert(parent_id);
1000    }
1001
1002    pub fn add_node(&mut self, stage: QueryStage) {
1003        // Insert here so that left/root stages also has linkage.
1004        self.child_edges.insert(stage.id, HashSet::new());
1005        self.parent_edges.insert(stage.id, HashSet::new());
1006        self.stages.insert(stage.id, stage);
1007    }
1008}
1009
1010impl BatchPlanFragmenter {
1011    /// After split, the `stage_graph` in the framenter may has the stage with incomplete source
1012    /// info, we need to fetch the source info to complete the stage in this function.
1013    /// Why separate this two step(`split()` and `generate_complete_query()`)?
1014    /// The step of fetching source info is a async operation so that we can't do it in the split
1015    /// step.
1016    pub async fn generate_complete_query(self) -> SchedulerResult<Query> {
1017        let stage_graph = self.stage_graph.unwrap();
1018        let new_stage_graph = stage_graph
1019            .complete(&self.catalog_reader, &self.worker_node_manager)
1020            .await?;
1021        Ok(Query {
1022            query_id: self.query_id,
1023            stage_graph: new_stage_graph,
1024        })
1025    }
1026
1027    fn new_stage(
1028        &mut self,
1029        root: PlanRef,
1030        exchange_info: Option<ExchangeInfo>,
1031    ) -> SchedulerResult<StageId> {
1032        let next_stage_id = self.next_stage_id;
1033        self.next_stage_id.inc();
1034
1035        let mut table_scan_info = None;
1036        let mut source_info = None;
1037        let mut file_scan_info = None;
1038
1039        // For current implementation, we can guarantee that each stage has only one table
1040        // scan(except System table) or one source.
1041        if let Some(info) = self.collect_stage_table_scan(root.clone())? {
1042            table_scan_info = Some(info);
1043        } else if let Some(info) = Self::collect_stage_source(root.clone())? {
1044            source_info = Some(info);
1045        } else if let Some(info) = Self::collect_stage_file_scan(root.clone())? {
1046            file_scan_info = Some(info);
1047        }
1048
1049        let mut has_lookup_join = false;
1050        let parallelism = match root.distribution() {
1051            Distribution::Single => {
1052                // A lookup join on a lookup table with a singleton distribution is gathered
1053                // into a single task. Mark `has_lookup_join` so that epoch unpin is delayed
1054                // until the end of the query.
1055                has_lookup_join = self
1056                    .collect_stage_lookup_join_parallelism(root.clone())?
1057                    .is_some();
1058
1059                if let Some(info) = &mut table_scan_info {
1060                    if let Some(partitions) = &mut info.partitions {
1061                        if partitions.len() != 1 {
1062                            // This is rare case, but it's possible on the internal state of the
1063                            // Source operator.
1064                            tracing::warn!(
1065                                "The stage has single distribution, but contains a scan of table `{}` with {} partitions. A single random worker will be assigned",
1066                                info.name,
1067                                partitions.len()
1068                            );
1069
1070                            *partitions = partitions
1071                                .drain()
1072                                .take(1)
1073                                .update(|(_, info)| {
1074                                    info.vnode_bitmap = Bitmap::ones(info.vnode_bitmap.len());
1075                                })
1076                                .collect();
1077                        }
1078                    } else {
1079                        // System table
1080                    }
1081                } else if source_info.is_some() {
1082                    return Err(SchedulerError::Internal(anyhow!(
1083                        "The stage has single distribution, but contains a source operator"
1084                    )));
1085                }
1086                1
1087            }
1088            _ => {
1089                if let Some(table_scan_info) = &table_scan_info {
1090                    table_scan_info
1091                        .partitions
1092                        .as_ref()
1093                        .map(|m| m.len())
1094                        .unwrap_or(1)
1095                } else if let Some(lookup_join_parallelism) =
1096                    self.collect_stage_lookup_join_parallelism(root.clone())?
1097                {
1098                    has_lookup_join = true;
1099                    lookup_join_parallelism
1100                } else if source_info.is_some() {
1101                    0
1102                } else if file_scan_info.is_some() {
1103                    1
1104                } else {
1105                    self.batch_parallelism
1106                }
1107            }
1108        };
1109        if source_info.is_none() && file_scan_info.is_none() && parallelism == 0 {
1110            return Err(BatchError::EmptyWorkerNodes.into());
1111        }
1112        let parallelism = if parallelism == 0 {
1113            None
1114        } else {
1115            Some(parallelism as u32)
1116        };
1117        let dml_table_id = Self::collect_dml_table_id(&root);
1118        let mut builder = QueryStageBuilder::new(
1119            next_stage_id,
1120            parallelism,
1121            exchange_info,
1122            table_scan_info,
1123            source_info,
1124            file_scan_info,
1125            has_lookup_join,
1126            dml_table_id,
1127            root.ctx().session_ctx().session_id(),
1128            root.ctx()
1129                .session_ctx()
1130                .config()
1131                .batch_enable_distributed_dml(),
1132        );
1133
1134        self.visit_node(root, &mut builder, None)?;
1135
1136        Ok(builder.finish(self.stage_graph_builder.as_mut().unwrap()))
1137    }
1138
1139    fn visit_node(
1140        &mut self,
1141        node: PlanRef,
1142        builder: &mut QueryStageBuilder,
1143        parent_exec_node: Option<&mut ExecutionPlanNode>,
1144    ) -> SchedulerResult<()> {
1145        match node.node_type() {
1146            BatchPlanNodeType::BatchExchange => {
1147                self.visit_exchange(node, builder, parent_exec_node)?;
1148            }
1149            _ => {
1150                let mut execution_plan_node = ExecutionPlanNode::try_from(node.clone())?;
1151
1152                for child in node.inputs() {
1153                    self.visit_node(child, builder, Some(&mut execution_plan_node))?;
1154                }
1155
1156                if let Some(parent) = parent_exec_node {
1157                    parent.children.push(execution_plan_node);
1158                } else {
1159                    builder.root = Some(execution_plan_node);
1160                }
1161            }
1162        }
1163        Ok(())
1164    }
1165
1166    fn visit_exchange(
1167        &mut self,
1168        node: PlanRef,
1169        builder: &mut QueryStageBuilder,
1170        parent_exec_node: Option<&mut ExecutionPlanNode>,
1171    ) -> SchedulerResult<()> {
1172        let mut execution_plan_node = ExecutionPlanNode::try_from(node.clone())?;
1173        let child_exchange_info = if let Some(parallelism) = builder.parallelism {
1174            Some(node.distribution().to_prost(
1175                parallelism,
1176                &self.catalog_reader,
1177                &self.worker_node_manager,
1178                self.batch_parallelism,
1179            )?)
1180        } else {
1181            None
1182        };
1183        let child_stage_id = self.new_stage(node.inputs()[0].clone(), child_exchange_info)?;
1184        execution_plan_node.source_stage_id = Some(child_stage_id);
1185        if builder.parallelism.is_none() {
1186            builder
1187                .children_exchange_distribution
1188                .insert(child_stage_id, node.distribution().clone());
1189        }
1190
1191        if let Some(parent) = parent_exec_node {
1192            parent.children.push(execution_plan_node);
1193        } else {
1194            builder.root = Some(execution_plan_node);
1195        }
1196
1197        builder.children_stages.push(child_stage_id);
1198        Ok(())
1199    }
1200
1201    /// Check whether this stage contains a source node.
1202    /// If so, use  `SplitEnumeratorImpl` to get the split info from exteneral source.
1203    ///
1204    /// For current implementation, we can guarantee that each stage has only one source.
1205    fn collect_stage_source(node: PlanRef) -> SchedulerResult<Option<SourceScanInfo>> {
1206        if node.node_type() == BatchPlanNodeType::BatchExchange {
1207            // Do not visit next stage.
1208            return Ok(None);
1209        }
1210
1211        if let Some(batch_kafka_node) = node.as_batch_kafka_scan() {
1212            let batch_kafka_scan: &BatchKafkaScan = batch_kafka_node;
1213            let source_catalog = batch_kafka_scan.source_catalog();
1214            if let Some(source_catalog) = source_catalog {
1215                let property =
1216                    ConnectorProperties::extract(source_catalog.with_properties.clone(), false)?;
1217                let timestamp_bound = batch_kafka_scan.kafka_timestamp_range_value();
1218                return Ok(Some(SourceScanInfo::new(SourceFetchInfo {
1219                    schema: batch_kafka_scan.base.schema().clone(),
1220                    connector: property,
1221                    fetch_parameters: SourceFetchParameters::KafkaTimebound {
1222                        lower: timestamp_bound.0,
1223                        upper: timestamp_bound.1,
1224                    },
1225                })));
1226            }
1227        } else if let Some(batch_iceberg_scan) = node.as_batch_iceberg_scan() {
1228            let batch_iceberg_scan: &BatchIcebergScan = batch_iceberg_scan;
1229            let task = batch_iceberg_scan.task.clone();
1230            let limit = batch_iceberg_scan.limit();
1231            return Ok(Some(SourceScanInfo::Unpartitioned(
1232                UnpartitionedData::Iceberg { task, limit },
1233            )));
1234        } else if let Some(source_node) = node.as_batch_source() {
1235            // TODO: use specific batch operator instead of batch source.
1236            let source_node: &BatchSource = source_node;
1237            let source_catalog = source_node.source_catalog();
1238            if let Some(source_catalog) = source_catalog {
1239                let property =
1240                    ConnectorProperties::extract(source_catalog.with_properties.clone(), false)?;
1241                return Ok(Some(SourceScanInfo::new(SourceFetchInfo {
1242                    schema: source_node.base.schema().clone(),
1243                    connector: property,
1244                    fetch_parameters: SourceFetchParameters::Empty,
1245                })));
1246            }
1247        }
1248
1249        node.inputs()
1250            .into_iter()
1251            .find_map(|n| Self::collect_stage_source(n).transpose())
1252            .transpose()
1253    }
1254
1255    fn collect_stage_file_scan(node: PlanRef) -> SchedulerResult<Option<FileScanInfo>> {
1256        if node.node_type() == BatchPlanNodeType::BatchExchange {
1257            // Do not visit next stage.
1258            return Ok(None);
1259        }
1260
1261        if let Some(batch_file_scan) = node.as_batch_file_scan() {
1262            return Ok(Some(FileScanInfo {
1263                file_location: batch_file_scan.core.file_location(),
1264            }));
1265        }
1266
1267        node.inputs()
1268            .into_iter()
1269            .find_map(|n| Self::collect_stage_file_scan(n).transpose())
1270            .transpose()
1271    }
1272
1273    /// Check whether this stage contains a table scan node and the table's information if so.
1274    ///
1275    /// If there are multiple scan nodes in this stage, they must have the same distribution, but
1276    /// maybe different vnodes partition. We just use the same partition for all the scan nodes.
1277    fn collect_stage_table_scan(&self, node: PlanRef) -> SchedulerResult<Option<TableScanInfo>> {
1278        let build_table_scan_info = |name, table_catalog: &TableCatalog, scan_range| {
1279            let vnode_mapping = self
1280                .worker_node_manager
1281                .fragment_mapping(table_catalog.fragment_id, self.batch_parallelism)?;
1282            let partitions = derive_partitions(scan_range, table_catalog, &vnode_mapping)?;
1283            let info = TableScanInfo::new(name, partitions);
1284            Ok(Some(info))
1285        };
1286        if node.node_type() == BatchPlanNodeType::BatchExchange {
1287            // Do not visit next stage.
1288            return Ok(None);
1289        }
1290        if let Some(scan_node) = node.as_batch_sys_seq_scan() {
1291            let name = scan_node.core().table.name.clone();
1292            Ok(Some(TableScanInfo::system_table(name)))
1293        } else if let Some(scan_node) = node.as_batch_log_seq_scan() {
1294            build_table_scan_info(
1295                scan_node.core().table_name.clone(),
1296                &scan_node.core().table,
1297                &[],
1298            )
1299        } else if let Some(scan_node) = node.as_batch_seq_scan() {
1300            build_table_scan_info(
1301                scan_node.core().table_name().to_owned(),
1302                &scan_node.core().table_catalog,
1303                scan_node.scan_ranges(),
1304            )
1305        } else {
1306            node.inputs()
1307                .into_iter()
1308                .find_map(|n| self.collect_stage_table_scan(n).transpose())
1309                .transpose()
1310        }
1311    }
1312
1313    /// Returns the dml table id if any.
1314    fn collect_dml_table_id(node: &PlanRef) -> Option<TableId> {
1315        if node.node_type() == BatchPlanNodeType::BatchExchange {
1316            return None;
1317        }
1318        if let Some(insert) = node.as_batch_insert() {
1319            Some(insert.core.table_id)
1320        } else if let Some(update) = node.as_batch_update() {
1321            Some(update.core.table_id)
1322        } else if let Some(delete) = node.as_batch_delete() {
1323            Some(delete.core.table_id)
1324        } else {
1325            node.inputs()
1326                .into_iter()
1327                .find_map(|n| Self::collect_dml_table_id(&n))
1328        }
1329    }
1330
1331    fn collect_stage_lookup_join_parallelism(
1332        &self,
1333        node: PlanRef,
1334    ) -> SchedulerResult<Option<usize>> {
1335        if node.node_type() == BatchPlanNodeType::BatchExchange {
1336            // Do not visit next stage.
1337            return Ok(None);
1338        }
1339        if let Some(lookup_join) = node.as_batch_lookup_join() {
1340            let table_catalog = lookup_join.right_table();
1341            let vnode_mapping = self
1342                .worker_node_manager
1343                .fragment_mapping(table_catalog.fragment_id, self.batch_parallelism)?;
1344            let parallelism = vnode_mapping.iter().sorted().dedup().count();
1345            Ok(Some(parallelism))
1346        } else {
1347            node.inputs()
1348                .into_iter()
1349                .find_map(|n| self.collect_stage_lookup_join_parallelism(n).transpose())
1350                .transpose()
1351        }
1352    }
1353}
1354
1355/// Try to derive the partition to read from the scan range.
1356/// It can be derived if the value of the distribution key is already known.
1357fn derive_partitions(
1358    scan_ranges: &[ScanRange],
1359    table_catalog: &TableCatalog,
1360    vnode_mapping: &WorkerSlotMapping,
1361) -> SchedulerResult<HashMap<WorkerSlotId, TablePartitionInfo>> {
1362    let vnode_mapping = if table_catalog.vnode_count.value() != vnode_mapping.len() {
1363        // The vnode count mismatch occurs only in special cases where a hash-distributed fragment
1364        // contains singleton internal tables. e.g., the state table of `Source` executors.
1365        // In this case, we reduce the vnode mapping to a single vnode as only `SINGLETON_VNODE` is used.
1366        assert_eq!(
1367            table_catalog.vnode_count.value(),
1368            1,
1369            "fragment vnode count {} does not match table vnode count {}",
1370            vnode_mapping.len(),
1371            table_catalog.vnode_count.value(),
1372        );
1373        &WorkerSlotMapping::new_single(vnode_mapping.iter().next().unwrap())
1374    } else {
1375        vnode_mapping
1376    };
1377    let vnode_count = vnode_mapping.len();
1378
1379    let mut partitions: HashMap<WorkerSlotId, (BitmapBuilder, Vec<_>)> = HashMap::new();
1380
1381    if scan_ranges.is_empty() {
1382        return Ok(vnode_mapping
1383            .to_bitmaps()
1384            .into_iter()
1385            .map(|(k, vnode_bitmap)| {
1386                (
1387                    k,
1388                    TablePartitionInfo {
1389                        vnode_bitmap,
1390                        scan_ranges: vec![],
1391                    },
1392                )
1393            })
1394            .collect());
1395    }
1396
1397    let table_distribution = TableDistribution::new_from_storage_table_desc(
1398        Some(Bitmap::ones(vnode_count).into()),
1399        &table_catalog.table_desc().try_to_protobuf()?,
1400    );
1401
1402    for scan_range in scan_ranges {
1403        let vnode = scan_range.try_compute_vnode(&table_distribution);
1404        match vnode {
1405            None => {
1406                // put this scan_range to all partitions
1407                vnode_mapping.to_bitmaps().into_iter().for_each(
1408                    |(worker_slot_id, vnode_bitmap)| {
1409                        let (bitmap, scan_ranges) = partitions
1410                            .entry(worker_slot_id)
1411                            .or_insert_with(|| (BitmapBuilder::zeroed(vnode_count), vec![]));
1412                        vnode_bitmap
1413                            .iter()
1414                            .enumerate()
1415                            .for_each(|(vnode, b)| bitmap.set(vnode, b));
1416                        scan_ranges.push(scan_range.to_protobuf());
1417                    },
1418                );
1419            }
1420            // scan a single partition
1421            Some(vnode) => {
1422                let worker_slot_id = vnode_mapping[vnode];
1423                let (bitmap, scan_ranges) = partitions
1424                    .entry(worker_slot_id)
1425                    .or_insert_with(|| (BitmapBuilder::zeroed(vnode_count), vec![]));
1426                bitmap.set(vnode.to_index(), true);
1427                scan_ranges.push(scan_range.to_protobuf());
1428            }
1429        }
1430    }
1431
1432    Ok(partitions
1433        .into_iter()
1434        .map(|(k, (bitmap, scan_ranges))| {
1435            (
1436                k,
1437                TablePartitionInfo {
1438                    vnode_bitmap: bitmap.finish(),
1439                    scan_ranges,
1440                },
1441            )
1442        })
1443        .collect())
1444}
1445
1446#[cfg(test)]
1447mod tests {
1448    use std::collections::{HashMap, HashSet};
1449
1450    use risingwave_pb::batch_plan::plan_node::NodeBody;
1451
1452    use crate::optimizer::plan_node::BatchPlanNodeType;
1453    use crate::scheduler::plan_fragmenter::StageId;
1454
1455    #[tokio::test]
1456    async fn test_fragmenter() {
1457        let query = crate::scheduler::distributed::tests::create_query().await;
1458
1459        assert_eq!(query.stage_graph.root_stage_id, 0.into());
1460        assert_eq!(query.stage_graph.stages.len(), 4);
1461
1462        // Check the mappings of child edges.
1463        assert_eq!(
1464            query.stage_graph.child_edges[&0.into()],
1465            HashSet::from_iter([1.into()])
1466        );
1467        assert_eq!(
1468            query.stage_graph.child_edges[&1.into()],
1469            HashSet::from_iter([2.into(), 3.into()])
1470        );
1471        assert_eq!(query.stage_graph.child_edges[&2.into()], HashSet::new());
1472        assert_eq!(query.stage_graph.child_edges[&3.into()], HashSet::new());
1473
1474        // Check the mappings of parent edges.
1475        assert_eq!(query.stage_graph.parent_edges[&0.into()], HashSet::new());
1476        assert_eq!(
1477            query.stage_graph.parent_edges[&1.into()],
1478            HashSet::from_iter([0.into()])
1479        );
1480        assert_eq!(
1481            query.stage_graph.parent_edges[&2.into()],
1482            HashSet::from_iter([1.into()])
1483        );
1484        assert_eq!(
1485            query.stage_graph.parent_edges[&3.into()],
1486            HashSet::from_iter([1.into()])
1487        );
1488
1489        // Verify topology order
1490        {
1491            let stage_id_to_pos: HashMap<StageId, usize> = query
1492                .stage_graph
1493                .stage_ids_by_topo_order()
1494                .enumerate()
1495                .map(|(pos, stage_id)| (stage_id, pos))
1496                .collect();
1497
1498            for stage_id in query.stage_graph.stages.keys() {
1499                let stage_pos = stage_id_to_pos[stage_id];
1500                for child_stage_id in &query.stage_graph.child_edges[stage_id] {
1501                    let child_pos = stage_id_to_pos[child_stage_id];
1502                    assert!(stage_pos > child_pos);
1503                }
1504            }
1505        }
1506
1507        // Check plan node in each stages.
1508        let root_exchange = query.stage_graph.stages.get(&0.into()).unwrap();
1509        assert_eq!(
1510            root_exchange.root.node_type(),
1511            BatchPlanNodeType::BatchExchange
1512        );
1513        assert_eq!(root_exchange.root.source_stage_id, Some(1.into()));
1514        assert!(matches!(root_exchange.root.node, NodeBody::Exchange(_)));
1515        assert_eq!(root_exchange.parallelism, Some(1));
1516        assert!(!root_exchange.has_table_scan());
1517
1518        let join_node = query.stage_graph.stages.get(&1.into()).unwrap();
1519        assert_eq!(join_node.root.node_type(), BatchPlanNodeType::BatchHashJoin);
1520        assert_eq!(join_node.parallelism, Some(24));
1521
1522        assert!(matches!(join_node.root.node, NodeBody::HashJoin(_)));
1523        assert_eq!(join_node.root.source_stage_id, None);
1524        assert_eq!(2, join_node.root.children.len());
1525
1526        assert!(matches!(
1527            join_node.root.children[0].node,
1528            NodeBody::Exchange(_)
1529        ));
1530        assert_eq!(join_node.root.children[0].source_stage_id, Some(2.into()));
1531        assert_eq!(0, join_node.root.children[0].children.len());
1532
1533        assert!(matches!(
1534            join_node.root.children[1].node,
1535            NodeBody::Exchange(_)
1536        ));
1537        assert_eq!(join_node.root.children[1].source_stage_id, Some(3.into()));
1538        assert_eq!(0, join_node.root.children[1].children.len());
1539        assert!(!join_node.has_table_scan());
1540
1541        let scan_node1 = query.stage_graph.stages.get(&2.into()).unwrap();
1542        assert_eq!(scan_node1.root.node_type(), BatchPlanNodeType::BatchSeqScan);
1543        assert_eq!(scan_node1.root.source_stage_id, None);
1544        assert_eq!(0, scan_node1.root.children.len());
1545        assert!(scan_node1.has_table_scan());
1546
1547        let scan_node2 = query.stage_graph.stages.get(&3.into()).unwrap();
1548        assert_eq!(scan_node2.root.node_type(), BatchPlanNodeType::BatchFilter);
1549        assert_eq!(scan_node2.root.source_stage_id, None);
1550        assert_eq!(1, scan_node2.root.children.len());
1551        assert!(scan_node2.has_table_scan());
1552    }
1553}