Skip to main content

risingwave_meta/barrier/
edge_builder.rs

1// Copyright 2025 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::collections::{HashMap, HashSet};
16use std::marker::PhantomData;
17use std::mem::take;
18
19use anyhow::anyhow;
20use risingwave_common::bitmap::Bitmap;
21use risingwave_meta_model::WorkerId;
22use risingwave_meta_model::fragment::DistributionType;
23use risingwave_pb::common::{ActorInfo, HostAddress};
24use risingwave_pb::id::{PartialGraphId, SubscriberId};
25use risingwave_pb::stream_plan::update_mutation::{DispatcherUpdate, MergeUpdate};
26use risingwave_pb::stream_plan::{AddMutation, PbDispatcher, StreamNode, UpdateMutation};
27use tracing::warn;
28
29use crate::MetaResult;
30use crate::barrier::rpc::ControlStreamManager;
31use crate::controller::fragment::InflightFragmentInfo;
32use crate::controller::utils::compose_dispatchers;
33use crate::model::{
34    ActorId, ActorNewNoShuffle, ActorUpstreams, DownstreamFragmentRelation, Fragment,
35    FragmentActorDispatchers, FragmentDownstreamRelation, FragmentId, StreamActor,
36    StreamJobActorsToCreate,
37};
38
39type ComposedEdge = (
40    HashMap<ActorId, PbDispatcher>,
41    HashMap<ActorId, ActorUpstreams>,
42    Option<HashMap<ActorId, ActorId>>,
43);
44
45/// Fragment information needed by [`FragmentEdgeBuilder`] to compute dispatchers and merge nodes.
46///
47/// Contains actor bitmaps and resolved host addresses.
48#[derive(Debug)]
49struct EdgeBuilderFragmentInfo {
50    distribution_type: DistributionType,
51    actors: HashMap<ActorId, Option<Bitmap>>,
52    actor_location: HashMap<ActorId, HostAddress>,
53    partial_graph_id: PartialGraphId,
54}
55
56#[derive(Debug)]
57enum FragmentStatus {
58    Existing(EdgeBuilderFragmentInfo),
59    /// Actors that will be newly instantiated, including actors of a persisted dormant fragment.
60    New(EdgeBuilderFragmentInfo),
61    /// The fragment remains persisted, but its edges are removed from the runtime graph.
62    /// The caller remains responsible for adding its actors to the mutation's drop list.
63    Stopping(EdgeBuilderFragmentInfo),
64    Changed {
65        before: EdgeBuilderFragmentInfo,
66        after: EdgeBuilderFragmentInfo,
67    },
68}
69
70impl EdgeBuilderFragmentInfo {
71    /// Build from an already-inflight fragment (actors already materialized).
72    fn from_inflight(
73        info: &InflightFragmentInfo,
74        partial_graph_id: PartialGraphId,
75        control_stream_manager: &ControlStreamManager,
76    ) -> Self {
77        let (actors, actor_location) = info
78            .actors
79            .iter()
80            .map(|(&actor_id, actor)| {
81                (
82                    (actor_id, actor.vnode_bitmap.clone()),
83                    (actor_id, control_stream_manager.host_addr(actor.worker_id)),
84                )
85            })
86            .unzip();
87        Self {
88            distribution_type: info.distribution_type,
89            actors,
90            actor_location,
91            partial_graph_id,
92        }
93    }
94
95    /// Build from a model `Fragment` with separately provided actors and locations.
96    fn from_fragment(
97        fragment: &Fragment,
98        stream_actors: &HashMap<FragmentId, Vec<StreamActor>>,
99        actor_worker: &HashMap<ActorId, WorkerId>,
100        partial_graph_id: PartialGraphId,
101        control_stream_manager: &ControlStreamManager,
102    ) -> Self {
103        let (actors, actor_location) = stream_actors
104            .get(&fragment.fragment_id)
105            .into_iter()
106            .flatten()
107            .map(|actor| {
108                (
109                    (actor.actor_id, actor.vnode_bitmap.clone()),
110                    (
111                        actor.actor_id,
112                        control_stream_manager.host_addr(actor_worker[&actor.actor_id]),
113                    ),
114                )
115            })
116            .unzip();
117        Self {
118            distribution_type: fragment.distribution_type.into(),
119            actors,
120            actor_location,
121            partial_graph_id,
122        }
123    }
124}
125
126#[derive(Debug)]
127pub(crate) struct FragmentEdgeBuildResult {
128    upstreams: HashMap<FragmentId, HashMap<ActorId, ActorUpstreams>>,
129    dispatchers: FragmentActorDispatchers,
130    merge_updates: HashMap<FragmentId, Vec<MergeUpdate>>,
131    dispatcher_updates: Vec<DispatcherUpdate>,
132}
133
134impl FragmentEdgeBuildResult {
135    fn validate_terminal_consumption(&self) {
136        let remaining_upstreams = self.upstreams.values().map(HashMap::len).sum::<usize>();
137        let remaining_dispatchers = self.dispatchers.values().map(HashMap::len).sum::<usize>();
138        let unapplied_dispatcher_updates = self.dispatcher_updates.len();
139        let unapplied_merge_updates = self.merge_updates.values().map(Vec::len).sum::<usize>();
140        let has_unconsumed_fields = remaining_upstreams != 0
141            || remaining_dispatchers != 0
142            || unapplied_dispatcher_updates != 0
143            || unapplied_merge_updates != 0;
144
145        debug_assert!(
146            !has_unconsumed_fields,
147            "edge result has unconsumed fields: remaining_upstreams={remaining_upstreams}, remaining_dispatchers={remaining_dispatchers}, unapplied_dispatcher_updates={unapplied_dispatcher_updates}, unapplied_merge_updates={unapplied_merge_updates}"
148        );
149        #[cfg(not(debug_assertions))]
150        if has_unconsumed_fields {
151            warn!(
152                remaining_upstreams,
153                remaining_dispatchers,
154                unapplied_dispatcher_updates,
155                unapplied_merge_updates,
156                "edge result has unconsumed fields"
157            );
158        }
159    }
160
161    /// Apply the remaining dispatchers after newly created actors have been collected.
162    pub(crate) fn apply_to_add_mutation(mut self, mutation: &mut AddMutation) {
163        let dispatchers = take(&mut self.dispatchers);
164        self.validate_terminal_consumption();
165        for (actor_id, dispatchers) in dispatchers.into_values().flatten() {
166            mutation
167                .actor_dispatchers
168                .entry(actor_id)
169                .or_default()
170                .dispatchers
171                .extend(dispatchers);
172        }
173    }
174
175    /// Apply the remaining edge updates after newly instantiated actors have collected their
176    /// edges. Any remaining upstreams target existing actors and therefore become merge updates.
177    /// Actor lifecycle is caller-owned, so this method does not modify `dropped_actors` for either
178    /// changed or stopping fragments.
179    pub(crate) fn apply_to_update_mutation(mut self, mutation: &mut UpdateMutation) {
180        let upstreams = take(&mut self.upstreams);
181        let dispatchers = take(&mut self.dispatchers);
182        let dispatcher_updates = take(&mut self.dispatcher_updates);
183        let merge_updates = take(&mut self.merge_updates);
184        self.validate_terminal_consumption();
185        mutation.dispatcher_update.extend(dispatcher_updates);
186        mutation
187            .merge_update
188            .extend(merge_updates.into_values().flatten());
189        for actor_upstreams in upstreams.into_values().flat_map(HashMap::into_iter) {
190            let (actor_id, actor_upstreams) = actor_upstreams;
191            for (upstream_fragment_id, added_upstream_actors) in actor_upstreams {
192                mutation.merge_update.push(MergeUpdate {
193                    actor_id,
194                    upstream_fragment_id,
195                    new_upstream_fragment_id: None,
196                    added_upstream_actors: added_upstream_actors.into_values().collect(),
197                    removed_upstream_actor_id: vec![],
198                });
199            }
200        }
201        for (actor_id, dispatchers) in dispatchers.into_values().flatten() {
202            mutation
203                .actor_new_dispatchers
204                .entry(actor_id)
205                .or_default()
206                .dispatchers
207                .extend(dispatchers);
208        }
209    }
210
211    pub(crate) fn collect_actors_to_create(
212        &mut self,
213        actors: impl Iterator<
214            Item = (
215                FragmentId,
216                &StreamNode,
217                impl Iterator<Item = (&StreamActor, WorkerId)>,
218                impl IntoIterator<Item = SubscriberId>,
219            ),
220        >,
221    ) -> StreamJobActorsToCreate {
222        let mut actors_to_create = StreamJobActorsToCreate::default();
223        for (fragment_id, node, actors, subscriber_ids) in actors {
224            let subscriber_ids: HashSet<_> = subscriber_ids.into_iter().collect();
225            for (actor, worker_id) in actors {
226                let upstreams = self
227                    .upstreams
228                    .get_mut(&fragment_id)
229                    .and_then(|upstreams| upstreams.remove(&actor.actor_id))
230                    .unwrap_or_default();
231                let dispatchers = self
232                    .dispatchers
233                    .get_mut(&fragment_id)
234                    .and_then(|upstreams| upstreams.remove(&actor.actor_id))
235                    .unwrap_or_default();
236                actors_to_create
237                    .entry(worker_id)
238                    .or_default()
239                    .entry(fragment_id)
240                    .or_insert_with(|| (node.clone(), vec![], subscriber_ids.clone()))
241                    .1
242                    .push((actor.clone(), upstreams, dispatchers))
243            }
244        }
245        actors_to_create
246    }
247
248    /// Take the common upstream actor set for an edge to an existing fragment.
249    ///
250    /// This is used by sink-into-table, whose hash edge connects every downstream actor to the
251    /// same set of upstream sink actors. The downstream actors already exist, so their upstream
252    /// edge is installed through `new_upstream_sinks` rather than actor creation.
253    pub(crate) fn take_common_upstream_actors(
254        &mut self,
255        upstream_fragment_id: FragmentId,
256        downstream_fragment_id: FragmentId,
257    ) -> MetaResult<Vec<ActorInfo>> {
258        let downstream_actor_upstreams = self
259            .upstreams
260            .get_mut(&downstream_fragment_id)
261            .ok_or_else(|| {
262                anyhow!("cannot find upstreams for downstream fragment {downstream_fragment_id}")
263            })?;
264        let mut common_upstream_actors = None;
265        for (&downstream_actor_id, actor_upstreams) in downstream_actor_upstreams.iter_mut() {
266            let upstream_actors = actor_upstreams
267                .remove(&upstream_fragment_id)
268                .ok_or_else(|| {
269                    anyhow!(
270                        "cannot find upstream fragment {upstream_fragment_id} for downstream actor {downstream_actor_id}"
271                    )
272                })?;
273            if let Some(common) = &common_upstream_actors {
274                // `HashMap` equality is order-independent. Compare the complete `ActorInfo` values
275                // as well because matching actor IDs with different routing metadata do not form a
276                // common upstream layout.
277                if common != &upstream_actors {
278                    return Err(anyhow!(
279                        "upstream actors from fragment {upstream_fragment_id} are not common across downstream fragment {downstream_fragment_id}"
280                    )
281                    .into());
282                }
283            } else {
284                common_upstream_actors = Some(upstream_actors);
285            }
286        }
287        let common_upstream_actors = common_upstream_actors
288            .ok_or_else(|| anyhow!("downstream fragment {downstream_fragment_id} has no actors"))?;
289
290        downstream_actor_upstreams.retain(|_, actor_upstreams| !actor_upstreams.is_empty());
291        if downstream_actor_upstreams.is_empty() {
292            self.upstreams.remove(&downstream_fragment_id);
293        }
294
295        let mut common_upstream_actors: Vec<_> = common_upstream_actors.into_values().collect();
296        common_upstream_actors.sort_unstable_by_key(|actor| actor.actor_id);
297        Ok(common_upstream_actors)
298    }
299}
300
301pub(crate) struct RegisteringFragments;
302pub(crate) struct AddingRelations;
303
304pub(crate) struct FragmentEdgeBuilder<State> {
305    fragments: HashMap<FragmentId, FragmentStatus>,
306    result: FragmentEdgeBuildResult,
307    actor_new_no_shuffle: ActorNewNoShuffle,
308    _state: PhantomData<State>,
309}
310
311impl FragmentEdgeBuilder<RegisteringFragments> {
312    pub(crate) fn new() -> Self {
313        Self {
314            fragments: Default::default(),
315            result: FragmentEdgeBuildResult {
316                upstreams: Default::default(),
317                dispatchers: Default::default(),
318                merge_updates: Default::default(),
319                dispatcher_updates: Default::default(),
320            },
321            actor_new_no_shuffle: Default::default(),
322            _state: PhantomData,
323        }
324    }
325
326    fn add_existing_fragment_infos(
327        &mut self,
328        fragment_infos: impl IntoIterator<Item = (FragmentId, EdgeBuilderFragmentInfo)>,
329    ) {
330        for (fragment_id, info) in fragment_infos {
331            self.fragments
332                .try_insert(fragment_id, FragmentStatus::Existing(info))
333                .expect("non-duplicate");
334        }
335    }
336
337    pub(crate) fn add_existing_fragments<'a>(
338        mut self,
339        fragments: impl IntoIterator<Item = &'a InflightFragmentInfo>,
340        partial_graph_id: PartialGraphId,
341        control_stream_manager: &ControlStreamManager,
342    ) -> Self {
343        self.add_existing_fragment_infos(fragments.into_iter().map(|fragment| {
344            (
345                fragment.fragment_id,
346                EdgeBuilderFragmentInfo::from_inflight(
347                    fragment,
348                    partial_graph_id,
349                    control_stream_manager,
350                ),
351            )
352        }));
353        self
354    }
355
356    fn add_new_fragment_infos(
357        &mut self,
358        fragment_infos: impl IntoIterator<Item = (FragmentId, EdgeBuilderFragmentInfo)>,
359    ) {
360        for (fragment_id, info) in fragment_infos {
361            self.fragments
362                .try_insert(fragment_id, FragmentStatus::New(info))
363                .expect("new fragment must not already be registered");
364        }
365    }
366
367    pub(crate) fn add_new_fragments<'a>(
368        mut self,
369        fragments: impl IntoIterator<Item = &'a InflightFragmentInfo>,
370        partial_graph_id: PartialGraphId,
371        control_stream_manager: &ControlStreamManager,
372    ) -> Self {
373        self.add_new_fragment_infos(fragments.into_iter().map(|fragment| {
374            (
375                fragment.fragment_id,
376                EdgeBuilderFragmentInfo::from_inflight(
377                    fragment,
378                    partial_graph_id,
379                    control_stream_manager,
380                ),
381            )
382        }));
383        self
384    }
385
386    /// Mark persisted fragments whose edges will be removed from the runtime graph.
387    /// The caller remains responsible for adding their actors to the mutation's drop list.
388    pub(crate) fn stop_existing_fragments(
389        mut self,
390        fragment_ids: impl IntoIterator<Item = FragmentId>,
391    ) -> Self {
392        for fragment_id in fragment_ids {
393            let status = self
394                .fragments
395                .remove(&fragment_id)
396                .expect("stopped fragment must already be registered");
397            let FragmentStatus::Existing(info) = status else {
398                panic!("fragment {fragment_id} must be existing before it is stopped");
399            };
400            self.fragments
401                .insert(fragment_id, FragmentStatus::Stopping(info));
402        }
403        self
404    }
405
406    pub(crate) fn add_new_logical_fragments<'a>(
407        mut self,
408        fragments: impl IntoIterator<Item = (PartialGraphId, &'a Fragment)>,
409        stream_actors: &HashMap<FragmentId, Vec<StreamActor>>,
410        actor_worker: &HashMap<ActorId, WorkerId>,
411        control_stream_manager: &ControlStreamManager,
412    ) -> Self {
413        self.add_new_fragment_infos(fragments.into_iter().map(|(partial_graph_id, fragment)| {
414            (
415                fragment.fragment_id,
416                EdgeBuilderFragmentInfo::from_fragment(
417                    fragment,
418                    stream_actors,
419                    actor_worker,
420                    partial_graph_id,
421                    control_stream_manager,
422                ),
423            )
424        }));
425        self
426    }
427
428    fn replace_existing_fragment_actor_infos(
429        &mut self,
430        fragment_infos: impl IntoIterator<Item = (FragmentId, EdgeBuilderFragmentInfo)>,
431    ) {
432        for (fragment_id, after) in fragment_infos {
433            let status = self
434                .fragments
435                .remove(&fragment_id)
436                .expect("changed fragment must already be registered");
437            let FragmentStatus::Existing(before) = status else {
438                panic!("fragment {fragment_id} can only be changed once");
439            };
440            assert_eq!(
441                before.distribution_type, after.distribution_type,
442                "fragment distribution type cannot change"
443            );
444            for actor_id in before.actors.keys() {
445                assert!(
446                    !after.actors.contains_key(actor_id),
447                    "changed fragment {fragment_id} must use entirely fresh actor ids: retained {actor_id}"
448                );
449            }
450            self.fragments
451                .insert(fragment_id, FragmentStatus::Changed { before, after });
452        }
453    }
454
455    pub(crate) fn replace_existing_fragment_actors<'a>(
456        mut self,
457        fragments: impl IntoIterator<Item = &'a InflightFragmentInfo>,
458        partial_graph_id: PartialGraphId,
459        control_stream_manager: &ControlStreamManager,
460    ) -> Self {
461        self.replace_existing_fragment_actor_infos(fragments.into_iter().map(|fragment| {
462            (
463                fragment.fragment_id,
464                EdgeBuilderFragmentInfo::from_inflight(
465                    fragment,
466                    partial_graph_id,
467                    control_stream_manager,
468                ),
469            )
470        }));
471        self
472    }
473
474    pub(crate) fn finish_fragments(self) -> FragmentEdgeBuilder<AddingRelations> {
475        FragmentEdgeBuilder {
476            fragments: self.fragments,
477            result: self.result,
478            actor_new_no_shuffle: self.actor_new_no_shuffle,
479            _state: PhantomData,
480        }
481    }
482}
483
484impl FragmentEdgeBuilder<AddingRelations> {
485    pub(crate) fn add_relations(
486        mut self,
487        relations: &FragmentDownstreamRelation,
488    ) -> MetaResult<Self> {
489        for fragment_id in self.fragments.keys().copied().collect::<Vec<_>>() {
490            let Some(relations) = relations.get(&fragment_id) else {
491                continue;
492            };
493            for relation in relations {
494                if self
495                    .fragments
496                    .contains_key(&relation.downstream_fragment_id)
497                {
498                    self.add_edge_inner(fragment_id, relation)?;
499                }
500            }
501        }
502        Ok(self)
503    }
504
505    /// Add a relation that is not yet installed in the runtime graph.
506    pub(crate) fn add_edge(
507        mut self,
508        fragment_id: FragmentId,
509        downstream: &DownstreamFragmentRelation,
510    ) -> MetaResult<Self> {
511        let installs_existing_relation = matches!(
512            (
513                self.fragments.get(&fragment_id),
514                self.fragments.get(&downstream.downstream_fragment_id),
515            ),
516            (
517                Some(FragmentStatus::Existing(_)),
518                Some(FragmentStatus::Existing(_)),
519            )
520        );
521        if installs_existing_relation {
522            self.add_existing_relation(fragment_id, downstream);
523        } else {
524            self.add_edge_inner(fragment_id, downstream)?;
525        }
526        Ok(self)
527    }
528
529    fn add_existing_relation(
530        &mut self,
531        fragment_id: FragmentId,
532        downstream: &DownstreamFragmentRelation,
533    ) {
534        let FragmentStatus::Existing(fragment) = &self.fragments[&fragment_id] else {
535            unreachable!("checked existing source fragment")
536        };
537        let FragmentStatus::Existing(downstream_fragment) =
538            &self.fragments[&downstream.downstream_fragment_id]
539        else {
540            unreachable!("checked existing downstream fragment")
541        };
542        let (dispatchers, upstreams, no_shuffle_map) =
543            Self::compose_edge(fragment_id, fragment, downstream, downstream_fragment);
544        Self::add_no_shuffle_mapping(
545            &mut self.actor_new_no_shuffle,
546            fragment_id,
547            downstream.downstream_fragment_id,
548            no_shuffle_map,
549        );
550        Self::add_dispatchers(&mut self.result.dispatchers, fragment_id, dispatchers);
551        Self::add_merge_updates(
552            &mut self.result.merge_updates,
553            fragment_id,
554            downstream.downstream_fragment_id,
555            downstream_fragment,
556            upstreams,
557            [],
558        );
559    }
560
561    fn add_edge_inner(
562        &mut self,
563        fragment_id: FragmentId,
564        downstream: &DownstreamFragmentRelation,
565    ) -> MetaResult<()> {
566        let Some(fragment_status) = self.fragments.get(&fragment_id) else {
567            if self
568                .fragments
569                .contains_key(&downstream.downstream_fragment_id)
570            {
571                return Err(anyhow!(
572                    "cannot find fragment {} with downstream {:?}",
573                    fragment_id,
574                    downstream
575                )
576                .into());
577            } else {
578                // ignore fragment relation with both upstream and downstream not in the set of fragments
579                return Ok(());
580            }
581        };
582        let Some(downstream_status) = self.fragments.get(&downstream.downstream_fragment_id) else {
583            // upstream is in the builder but downstream is not (e.g., edge to an independent job's fragment).
584            // Skip this edge.
585            return Ok(());
586        };
587        match (fragment_status, downstream_status) {
588            // `FragmentStatus` describes the lifecycle of the fragment's actor set, not whether
589            // the relation itself is new. Current mutation workflows only install relations where
590            // at least one endpoint is new or changed, so an existing-to-existing relation is
591            // already installed and needs no mutation.
592            (FragmentStatus::Existing(_), FragmentStatus::Existing(_)) => {}
593            (
594                FragmentStatus::Existing(fragment),
595                FragmentStatus::Changed {
596                    before,
597                    after: downstream_fragment,
598                },
599            ) => {
600                let (dispatchers, upstreams, no_shuffle_map) =
601                    Self::compose_edge(fragment_id, fragment, downstream, downstream_fragment);
602                Self::add_no_shuffle_mapping(
603                    &mut self.actor_new_no_shuffle,
604                    fragment_id,
605                    downstream.downstream_fragment_id,
606                    no_shuffle_map,
607                );
608                Self::add_upstreams(
609                    &mut self.result.upstreams,
610                    downstream.downstream_fragment_id,
611                    upstreams,
612                );
613                Self::add_dispatcher_updates(
614                    &mut self.result.dispatcher_updates,
615                    dispatchers,
616                    before.actors.keys().copied(),
617                );
618            }
619            (
620                FragmentStatus::Changed {
621                    before,
622                    after: fragment,
623                },
624                FragmentStatus::Existing(downstream_fragment),
625            ) => {
626                let (dispatchers, upstreams, no_shuffle_map) =
627                    Self::compose_edge(fragment_id, fragment, downstream, downstream_fragment);
628                Self::add_no_shuffle_mapping(
629                    &mut self.actor_new_no_shuffle,
630                    fragment_id,
631                    downstream.downstream_fragment_id,
632                    no_shuffle_map,
633                );
634                Self::add_dispatchers(&mut self.result.dispatchers, fragment_id, dispatchers);
635                Self::add_merge_updates(
636                    &mut self.result.merge_updates,
637                    fragment_id,
638                    downstream.downstream_fragment_id,
639                    downstream_fragment,
640                    upstreams,
641                    before.actors.keys().copied(),
642                );
643            }
644            (FragmentStatus::Stopping(fragment), FragmentStatus::Existing(downstream_fragment)) => {
645                Self::add_merge_updates(
646                    &mut self.result.merge_updates,
647                    fragment_id,
648                    downstream.downstream_fragment_id,
649                    downstream_fragment,
650                    HashMap::new(),
651                    fragment.actors.keys().copied(),
652                );
653            }
654            (FragmentStatus::Stopping(_), FragmentStatus::Stopping(_)) => {}
655            (FragmentStatus::New(_), FragmentStatus::Changed { .. })
656            | (FragmentStatus::Changed { .. }, FragmentStatus::New(_))
657            | (FragmentStatus::Existing(_), FragmentStatus::Stopping(_))
658            | (FragmentStatus::New(_), FragmentStatus::Stopping(_))
659            | (FragmentStatus::Changed { .. }, FragmentStatus::Stopping(_))
660            | (
661                FragmentStatus::Stopping(_),
662                FragmentStatus::New(_) | FragmentStatus::Changed { .. },
663            ) => {
664                return Err(anyhow!(
665                    "unsupported fragment lifecycle edge: {} -> {}",
666                    fragment_id,
667                    downstream.downstream_fragment_id,
668                )
669                .into());
670            }
671            (FragmentStatus::Existing(fragment), FragmentStatus::New(downstream_fragment))
672            | (FragmentStatus::New(fragment), FragmentStatus::Existing(downstream_fragment))
673            | (FragmentStatus::New(fragment), FragmentStatus::New(downstream_fragment))
674            | (
675                FragmentStatus::Changed {
676                    after: fragment, ..
677                },
678                FragmentStatus::Changed {
679                    after: downstream_fragment,
680                    ..
681                },
682            ) => {
683                let (dispatchers, upstreams, no_shuffle_map) =
684                    Self::compose_edge(fragment_id, fragment, downstream, downstream_fragment);
685                Self::add_no_shuffle_mapping(
686                    &mut self.actor_new_no_shuffle,
687                    fragment_id,
688                    downstream.downstream_fragment_id,
689                    no_shuffle_map,
690                );
691                Self::add_dispatchers(&mut self.result.dispatchers, fragment_id, dispatchers);
692                Self::add_upstreams(
693                    &mut self.result.upstreams,
694                    downstream.downstream_fragment_id,
695                    upstreams,
696                );
697            }
698        }
699        Ok(())
700    }
701
702    fn add_no_shuffle_mapping(
703        actor_new_no_shuffle: &mut ActorNewNoShuffle,
704        fragment_id: FragmentId,
705        downstream_fragment_id: FragmentId,
706        no_shuffle_map: Option<HashMap<ActorId, ActorId>>,
707    ) {
708        if let Some(no_shuffle_map) = no_shuffle_map {
709            actor_new_no_shuffle
710                .entry(fragment_id)
711                .or_default()
712                .insert(downstream_fragment_id, no_shuffle_map);
713        }
714    }
715
716    fn add_dispatchers(
717        fragment_dispatchers: &mut FragmentActorDispatchers,
718        fragment_id: FragmentId,
719        dispatchers: HashMap<ActorId, PbDispatcher>,
720    ) {
721        for (actor_id, dispatcher) in dispatchers {
722            fragment_dispatchers
723                .entry(fragment_id)
724                .or_default()
725                .entry(actor_id)
726                .or_default()
727                .push(dispatcher);
728        }
729    }
730
731    fn add_upstreams(
732        fragment_upstreams: &mut HashMap<FragmentId, HashMap<ActorId, ActorUpstreams>>,
733        fragment_id: FragmentId,
734        upstreams: HashMap<ActorId, ActorUpstreams>,
735    ) {
736        let target = fragment_upstreams.entry(fragment_id).or_default();
737        for (actor_id, actor_upstreams) in upstreams {
738            target.entry(actor_id).or_default().extend(actor_upstreams);
739        }
740    }
741
742    fn add_dispatcher_updates(
743        dispatcher_updates: &mut Vec<DispatcherUpdate>,
744        dispatchers: HashMap<ActorId, PbDispatcher>,
745        removed_downstream_actor_id: impl IntoIterator<Item = ActorId>,
746    ) {
747        let removed_downstream_actor_id: Vec<_> = removed_downstream_actor_id.into_iter().collect();
748        for (actor_id, dispatcher) in dispatchers {
749            dispatcher_updates.push(DispatcherUpdate {
750                actor_id,
751                dispatcher_id: dispatcher.dispatcher_id,
752                hash_mapping: dispatcher.hash_mapping,
753                added_downstream_actor_id: dispatcher.downstream_actor_id,
754                removed_downstream_actor_id: removed_downstream_actor_id.clone(),
755            });
756        }
757    }
758
759    fn add_merge_updates(
760        merge_updates: &mut HashMap<FragmentId, Vec<MergeUpdate>>,
761        fragment_id: FragmentId,
762        downstream_fragment_id: FragmentId,
763        downstream_fragment: &EdgeBuilderFragmentInfo,
764        upstreams: HashMap<ActorId, ActorUpstreams>,
765        removed_upstream_actor_id: impl IntoIterator<Item = ActorId>,
766    ) {
767        let removed_upstream_actor_id: Vec<_> = removed_upstream_actor_id.into_iter().collect();
768        let fragment_merge_updates = merge_updates.entry(downstream_fragment_id).or_default();
769        for actor_id in downstream_fragment.actors.keys() {
770            let added_upstream_actors = upstreams
771                .get(actor_id)
772                .and_then(|upstreams| upstreams.get(&fragment_id))
773                .into_iter()
774                .flat_map(|upstreams| upstreams.values().cloned())
775                .collect();
776            fragment_merge_updates.push(MergeUpdate {
777                actor_id: *actor_id,
778                upstream_fragment_id: fragment_id,
779                new_upstream_fragment_id: None,
780                added_upstream_actors,
781                removed_upstream_actor_id: removed_upstream_actor_id.clone(),
782            });
783        }
784    }
785
786    fn compose_edge_dispatchers(
787        fragment: &EdgeBuilderFragmentInfo,
788        downstream: &DownstreamFragmentRelation,
789        downstream_fragment: &EdgeBuilderFragmentInfo,
790    ) -> (
791        HashMap<ActorId, PbDispatcher>,
792        Option<HashMap<ActorId, ActorId>>,
793    ) {
794        compose_dispatchers(
795            fragment.distribution_type,
796            &fragment.actors,
797            downstream.downstream_fragment_id,
798            downstream_fragment.distribution_type,
799            &downstream_fragment.actors,
800            downstream.dispatcher_type,
801            downstream.dist_key_indices.clone(),
802            downstream.output_mapping.clone(),
803        )
804    }
805
806    fn compose_edge(
807        fragment_id: FragmentId,
808        fragment: &EdgeBuilderFragmentInfo,
809        downstream: &DownstreamFragmentRelation,
810        downstream_fragment: &EdgeBuilderFragmentInfo,
811    ) -> ComposedEdge {
812        let (dispatchers, no_shuffle_map) =
813            Self::compose_edge_dispatchers(fragment, downstream, downstream_fragment);
814        let mut upstreams: HashMap<ActorId, ActorUpstreams> = HashMap::new();
815        for (&actor_id, dispatcher) in &dispatchers {
816            let actor_location = &fragment.actor_location[&actor_id];
817            for &downstream_actor in &dispatcher.downstream_actor_id {
818                upstreams
819                    .entry(downstream_actor)
820                    .or_default()
821                    .entry(fragment_id)
822                    .or_default()
823                    .insert(
824                        actor_id,
825                        ActorInfo {
826                            actor_id,
827                            host: Some(actor_location.clone()),
828                            partial_graph_id: fragment.partial_graph_id,
829                        },
830                    );
831            }
832        }
833        (dispatchers, upstreams, no_shuffle_map)
834    }
835
836    pub(crate) fn replace_upstream(
837        mut self,
838        fragment_id: FragmentId,
839        original_upstream_fragment_id: FragmentId,
840        new_upstream_fragment_id: FragmentId,
841    ) -> Self {
842        let fragment_merge_updates = self.result.merge_updates.entry(fragment_id).or_default();
843        if let Some(fragment_upstreams) = self.result.upstreams.get_mut(&fragment_id) {
844            fragment_upstreams.retain(|&actor_id, actor_upstreams| {
845                if let Some(new_upstreams) = actor_upstreams.remove(&new_upstream_fragment_id) {
846                    fragment_merge_updates.push(MergeUpdate {
847                        actor_id,
848                        upstream_fragment_id: original_upstream_fragment_id,
849                        new_upstream_fragment_id: Some(new_upstream_fragment_id),
850                        added_upstream_actors: new_upstreams.into_values().collect(),
851                        removed_upstream_actor_id: vec![],
852                    })
853                } else if cfg!(debug_assertions) {
854                    panic!("cannot find new upstreams for actor {} in fragment {} to new_upstream {}. Current upstreams {:?}", actor_id, fragment_id, new_upstream_fragment_id, actor_upstreams);
855                } else {
856                    warn!(%actor_id, %fragment_id, %new_upstream_fragment_id, ?actor_upstreams, "cannot find new upstreams for actor");
857                }
858                !actor_upstreams.is_empty()
859            })
860        } else if cfg!(debug_assertions) {
861            panic!(
862                "cannot find new upstreams for fragment {} to new_upstream {} to replace {}. Current upstreams: {:?}",
863                fragment_id,
864                new_upstream_fragment_id,
865                original_upstream_fragment_id,
866                self.result.upstreams
867            );
868        } else {
869            warn!(%fragment_id, %new_upstream_fragment_id, %original_upstream_fragment_id, upstreams = ?self.result.upstreams, "cannot find new upstreams to replace");
870        }
871        self
872    }
873
874    /// Finalize the builder and return the generated edges and mutation-time deltas.
875    pub(crate) fn build(self) -> (FragmentEdgeBuildResult, ActorNewNoShuffle) {
876        (self.result, self.actor_new_no_shuffle)
877    }
878}
879
880#[cfg(test)]
881mod tests {
882    use risingwave_meta_model::DispatcherType;
883    use risingwave_pb::stream_plan::PbDispatchOutputMapping;
884
885    use super::*;
886
887    #[derive(Clone, Copy, Debug)]
888    enum TestStatus {
889        Existing,
890        New,
891        Stopping,
892        Changed,
893    }
894
895    fn actor(id: u32) -> ActorId {
896        ActorId::new(id)
897    }
898
899    fn fragment(id: u32) -> FragmentId {
900        FragmentId::new(id)
901    }
902
903    fn edge_info(
904        distribution_type: DistributionType,
905        actors: impl IntoIterator<Item = (u32, Option<Bitmap>)>,
906    ) -> EdgeBuilderFragmentInfo {
907        let actors: HashMap<_, _> = actors
908            .into_iter()
909            .map(|(id, bitmap)| (actor(id), bitmap))
910            .collect();
911        let actor_location = actors
912            .keys()
913            .map(|actor_id| {
914                (
915                    *actor_id,
916                    HostAddress {
917                        host: format!("actor-{actor_id}"),
918                        port: 1234,
919                    },
920                )
921            })
922            .collect();
923        EdgeBuilderFragmentInfo {
924            distribution_type,
925            actors,
926            actor_location,
927            partial_graph_id: PartialGraphId::new(1),
928        }
929    }
930
931    fn single_info(actor_id: u32) -> EdgeBuilderFragmentInfo {
932        edge_info(DistributionType::Single, [(actor_id, None)])
933    }
934
935    fn relation(target: FragmentId, dispatcher_type: DispatcherType) -> DownstreamFragmentRelation {
936        DownstreamFragmentRelation {
937            downstream_fragment_id: target,
938            dispatcher_type,
939            dist_key_indices: vec![],
940            output_mapping: PbDispatchOutputMapping::default(),
941        }
942    }
943
944    fn build_status_pair(
945        source: TestStatus,
946        target: TestStatus,
947    ) -> MetaResult<FragmentEdgeBuildResult> {
948        let source_fragment = fragment(1);
949        let target_fragment = fragment(2);
950        let existing = [(source_fragment, source, 1), (target_fragment, target, 11)]
951            .into_iter()
952            .filter(|(_, status, _)| !matches!(status, TestStatus::New))
953            .map(|(fragment_id, _, actor_id)| (fragment_id, single_info(actor_id)));
954        let mut builder = FragmentEdgeBuilder::new();
955        builder.add_existing_fragment_infos(existing);
956        for (fragment_id, status, actor_id) in
957            [(source_fragment, source, 2), (target_fragment, target, 12)]
958        {
959            match status {
960                TestStatus::Existing => {}
961                TestStatus::New => {
962                    builder.add_new_fragment_infos([(fragment_id, single_info(actor_id))]);
963                }
964                TestStatus::Stopping => {
965                    builder = builder.stop_existing_fragments([fragment_id]);
966                }
967                TestStatus::Changed => {
968                    builder.replace_existing_fragment_actor_infos([(
969                        fragment_id,
970                        single_info(actor_id),
971                    )]);
972                }
973            }
974        }
975        let (result, _) = builder
976            .finish_fragments()
977            .add_relations(&HashMap::from([(
978                source_fragment,
979                vec![relation(target_fragment, DispatcherType::Broadcast)],
980            )]))?
981            .build();
982        Ok(result)
983    }
984
985    #[test]
986    fn test_endpoint_status_matrix() {
987        for source in [
988            TestStatus::Existing,
989            TestStatus::New,
990            TestStatus::Stopping,
991            TestStatus::Changed,
992        ] {
993            for target in [
994                TestStatus::Existing,
995                TestStatus::New,
996                TestStatus::Stopping,
997                TestStatus::Changed,
998            ] {
999                let result = build_status_pair(source, target);
1000                if matches!(
1001                    (source, target),
1002                    (
1003                        TestStatus::Existing | TestStatus::New | TestStatus::Changed,
1004                        TestStatus::Stopping
1005                    ) | (TestStatus::Stopping, TestStatus::New | TestStatus::Changed)
1006                        | (TestStatus::New, TestStatus::Changed)
1007                        | (TestStatus::Changed, TestStatus::New)
1008                ) {
1009                    assert!(result.is_err(), "source={source:?}, target={target:?}");
1010                    continue;
1011                }
1012                let result = result.unwrap();
1013                assert_eq!(
1014                    result.dispatchers.contains_key(&fragment(1)),
1015                    matches!(source, TestStatus::New | TestStatus::Changed)
1016                        || matches!((source, target), (TestStatus::Existing, TestStatus::New)),
1017                    "source={source:?}, target={target:?}"
1018                );
1019                assert_eq!(
1020                    result.upstreams.contains_key(&fragment(2)),
1021                    matches!(target, TestStatus::New | TestStatus::Changed)
1022                        || matches!((source, target), (TestStatus::New, TestStatus::Existing)),
1023                    "source={source:?}, target={target:?}"
1024                );
1025                assert_eq!(
1026                    !result.dispatcher_updates.is_empty(),
1027                    matches!(
1028                        (source, target),
1029                        (TestStatus::Existing, TestStatus::Changed)
1030                    ),
1031                    "source={source:?}, target={target:?}"
1032                );
1033                assert_eq!(
1034                    result
1035                        .merge_updates
1036                        .values()
1037                        .any(|updates| !updates.is_empty()),
1038                    matches!(
1039                        (source, target),
1040                        (
1041                            TestStatus::Stopping | TestStatus::Changed,
1042                            TestStatus::Existing
1043                        )
1044                    ),
1045                    "source={source:?}, target={target:?}"
1046                );
1047            }
1048        }
1049    }
1050
1051    #[test]
1052    fn test_hash_and_broadcast_dispatcher_updates() {
1053        for dispatcher_type in [DispatcherType::Hash, DispatcherType::Broadcast] {
1054            let source = fragment(1);
1055            let target = fragment(2);
1056            let bitmap = || Some(Bitmap::from_iter([true, true]));
1057            let mut builder = FragmentEdgeBuilder::new();
1058            builder.add_existing_fragment_infos([
1059                (source, edge_info(DistributionType::Single, [(1, None)])),
1060                (target, edge_info(DistributionType::Hash, [(11, bitmap())])),
1061            ]);
1062            builder.replace_existing_fragment_actor_infos([(
1063                target,
1064                edge_info(DistributionType::Hash, [(12, bitmap())]),
1065            )]);
1066            let (result, _) = builder
1067                .finish_fragments()
1068                .add_edge(source, &relation(target, dispatcher_type))
1069                .unwrap()
1070                .build();
1071
1072            let update = &result.dispatcher_updates[0];
1073            assert_eq!(update.added_downstream_actor_id, vec![actor(12)]);
1074            assert_eq!(update.removed_downstream_actor_id, vec![actor(11)]);
1075            assert_eq!(
1076                update.hash_mapping.is_some(),
1077                dispatcher_type == DispatcherType::Hash
1078            );
1079        }
1080    }
1081
1082    #[test]
1083    fn test_no_shuffle_changed_fragments() {
1084        let source = fragment(1);
1085        let target = fragment(2);
1086        let mut builder = FragmentEdgeBuilder::new();
1087        builder.add_existing_fragment_infos([(source, single_info(1)), (target, single_info(11))]);
1088        builder.replace_existing_fragment_actor_infos([
1089            (source, single_info(2)),
1090            (target, single_info(12)),
1091        ]);
1092        let (result, actor_new_no_shuffle) = builder
1093            .finish_fragments()
1094            .add_edge(source, &relation(target, DispatcherType::NoShuffle))
1095            .unwrap()
1096            .build();
1097
1098        assert!(result.dispatcher_updates.is_empty());
1099        assert!(result.merge_updates.is_empty());
1100        assert_eq!(actor_new_no_shuffle[&source][&target][&actor(2)], actor(12));
1101    }
1102
1103    #[test]
1104    fn test_no_shuffle_updates_remove_before_actor_superset() {
1105        let source = fragment(1);
1106        let target = fragment(2);
1107        let actors = |left, right| {
1108            [
1109                (left, Some(Bitmap::from_iter([true, false]))),
1110                (right, Some(Bitmap::from_iter([false, true]))),
1111            ]
1112        };
1113        let relations =
1114            HashMap::from([(source, vec![relation(target, DispatcherType::NoShuffle)])]);
1115
1116        let mut builder = FragmentEdgeBuilder::new();
1117        builder.add_existing_fragment_infos([
1118            (source, edge_info(DistributionType::Hash, actors(1, 2))),
1119            (target, edge_info(DistributionType::Hash, actors(11, 12))),
1120        ]);
1121        builder.replace_existing_fragment_actor_infos([(
1122            target,
1123            edge_info(DistributionType::Hash, actors(13, 14)),
1124        )]);
1125        let (result, _) = builder
1126            .finish_fragments()
1127            .add_relations(&relations)
1128            .unwrap()
1129            .build();
1130
1131        assert_eq!(result.dispatcher_updates.len(), 2);
1132        for update in result.dispatcher_updates {
1133            assert_eq!(update.added_downstream_actor_id.len(), 1);
1134            let mut removed = update.removed_downstream_actor_id;
1135            removed.sort_unstable();
1136            assert_eq!(removed, vec![actor(11), actor(12)]);
1137        }
1138
1139        let mut builder = FragmentEdgeBuilder::new();
1140        builder.add_existing_fragment_infos([
1141            (source, edge_info(DistributionType::Hash, actors(1, 2))),
1142            (target, edge_info(DistributionType::Hash, actors(11, 12))),
1143        ]);
1144        builder.replace_existing_fragment_actor_infos([(
1145            source,
1146            edge_info(DistributionType::Hash, actors(3, 4)),
1147        )]);
1148        let (result, _) = builder
1149            .finish_fragments()
1150            .add_relations(&relations)
1151            .unwrap()
1152            .build();
1153
1154        let merge_updates = &result.merge_updates[&target];
1155        assert_eq!(merge_updates.len(), 2);
1156        for update in merge_updates {
1157            assert_eq!(update.added_upstream_actors.len(), 1);
1158            let mut removed = update.removed_upstream_actor_id.clone();
1159            removed.sort_unstable();
1160            assert_eq!(removed, vec![actor(1), actor(2)]);
1161        }
1162    }
1163
1164    #[test]
1165    fn test_replace_upstream() {
1166        let old_source = fragment(1);
1167        let new_source = fragment(2);
1168        let target = fragment(3);
1169        let mut builder = FragmentEdgeBuilder::new();
1170        builder.add_existing_fragment_infos([(target, single_info(11))]);
1171        builder.add_new_fragment_infos([(new_source, single_info(2))]);
1172        let (result, _) = builder
1173            .finish_fragments()
1174            .add_edge(new_source, &relation(target, DispatcherType::Broadcast))
1175            .unwrap()
1176            .replace_upstream(target, old_source, new_source)
1177            .build();
1178
1179        let update = &result.merge_updates[&target][0];
1180        assert_eq!(update.upstream_fragment_id, old_source);
1181        assert_eq!(update.new_upstream_fragment_id, Some(new_source));
1182        assert_eq!(update.added_upstream_actors[0].actor_id, actor(2));
1183    }
1184
1185    #[test]
1186    fn test_new_to_existing_upstreams_are_consumed_for_add_mutation() {
1187        let source = fragment(1);
1188        let target = fragment(2);
1189        let mut builder = FragmentEdgeBuilder::new();
1190        builder.add_existing_fragment_infos([(target, single_info(11))]);
1191        builder.add_new_fragment_infos([(source, single_info(1))]);
1192        let (mut result, _) = builder
1193            .finish_fragments()
1194            .add_edge(source, &relation(target, DispatcherType::Broadcast))
1195            .unwrap()
1196            .build();
1197        let source_actor = StreamActor {
1198            actor_id: actor(1),
1199            fragment_id: source,
1200            vnode_bitmap: None,
1201            mview_definition: Default::default(),
1202            expr_context: None,
1203            config_override: Default::default(),
1204        };
1205        let node = StreamNode::default();
1206        let worker_id: WorkerId = 1.into();
1207        let actors_to_create = result.collect_actors_to_create(std::iter::once((
1208            source,
1209            &node,
1210            std::iter::once((&source_actor, worker_id)),
1211            [],
1212        )));
1213
1214        assert_eq!(actors_to_create[&worker_id][&source].1[0].2.len(), 1);
1215        let upstream_actors = result.take_common_upstream_actors(source, target).unwrap();
1216        assert_eq!(upstream_actors.len(), 1);
1217        assert_eq!(upstream_actors[0].actor_id, actor(1));
1218        result.apply_to_add_mutation(&mut AddMutation::default());
1219    }
1220
1221    #[test]
1222    fn test_take_common_upstream_actors_from_hash_edge() {
1223        let source = fragment(1);
1224        let target = fragment(2);
1225        let actors = |left, right| {
1226            [
1227                (left, Some(Bitmap::from_iter([true, false]))),
1228                (right, Some(Bitmap::from_iter([false, true]))),
1229            ]
1230        };
1231        let mut builder = FragmentEdgeBuilder::new();
1232        builder.add_existing_fragment_infos([(
1233            target,
1234            edge_info(DistributionType::Hash, actors(11, 12)),
1235        )]);
1236        builder.add_new_fragment_infos([(source, edge_info(DistributionType::Hash, actors(1, 2)))]);
1237        let (mut result, _) = builder
1238            .finish_fragments()
1239            .add_edge(source, &relation(target, DispatcherType::Hash))
1240            .unwrap()
1241            .build();
1242
1243        let upstream_actors = result.take_common_upstream_actors(source, target).unwrap();
1244        assert_eq!(
1245            upstream_actors
1246                .into_iter()
1247                .map(|actor| actor.actor_id)
1248                .collect::<Vec<_>>(),
1249            vec![actor(1), actor(2)]
1250        );
1251        assert!(!result.upstreams.contains_key(&target));
1252    }
1253
1254    #[test]
1255    #[should_panic(expected = "remaining_upstreams=1")]
1256    fn test_add_mutation_rejects_uncollected_actor_upstreams() {
1257        let source = fragment(1);
1258        let target = fragment(2);
1259        let mut builder = FragmentEdgeBuilder::new();
1260        builder.add_existing_fragment_infos([(source, single_info(1))]);
1261        builder.add_new_fragment_infos([(target, single_info(11))]);
1262        let (result, _) = builder
1263            .finish_fragments()
1264            .add_edge(source, &relation(target, DispatcherType::Broadcast))
1265            .unwrap()
1266            .build();
1267
1268        result.apply_to_add_mutation(&mut AddMutation::default());
1269    }
1270
1271    #[test]
1272    fn test_update_mutation_consumes_edge_updates_before_validation() {
1273        let source = fragment(1);
1274        let mut result = build_status_pair(TestStatus::Changed, TestStatus::Existing).unwrap();
1275        let source_actor = StreamActor {
1276            actor_id: actor(2),
1277            fragment_id: source,
1278            vnode_bitmap: None,
1279            mview_definition: Default::default(),
1280            expr_context: None,
1281            config_override: Default::default(),
1282        };
1283        let node = StreamNode::default();
1284        result.collect_actors_to_create(std::iter::once((
1285            source,
1286            &node,
1287            std::iter::once((&source_actor, 1.into())),
1288            [],
1289        )));
1290        let mut mutation = UpdateMutation::default();
1291
1292        result.apply_to_update_mutation(&mut mutation);
1293
1294        assert_eq!(mutation.merge_update.len(), 1);
1295    }
1296
1297    #[test]
1298    fn test_update_mutation_does_not_infer_dropped_actors() {
1299        let caller_owned_drop = actor(99);
1300
1301        let mut changed = build_status_pair(TestStatus::Changed, TestStatus::Existing).unwrap();
1302        let changed_actor = StreamActor {
1303            actor_id: actor(2),
1304            fragment_id: fragment(1),
1305            vnode_bitmap: None,
1306            mview_definition: Default::default(),
1307            expr_context: None,
1308            config_override: Default::default(),
1309        };
1310        changed.collect_actors_to_create(std::iter::once((
1311            fragment(1),
1312            &StreamNode::default(),
1313            std::iter::once((&changed_actor, 1.into())),
1314            [],
1315        )));
1316        let mut changed_mutation = UpdateMutation {
1317            dropped_actors: vec![caller_owned_drop],
1318            ..Default::default()
1319        };
1320        changed.apply_to_update_mutation(&mut changed_mutation);
1321        assert_eq!(changed_mutation.dropped_actors, vec![caller_owned_drop]);
1322
1323        let stopping = build_status_pair(TestStatus::Stopping, TestStatus::Existing).unwrap();
1324        let mut stopping_mutation = UpdateMutation {
1325            dropped_actors: vec![caller_owned_drop],
1326            ..Default::default()
1327        };
1328        stopping.apply_to_update_mutation(&mut stopping_mutation);
1329        assert_eq!(stopping_mutation.dropped_actors, vec![caller_owned_drop]);
1330    }
1331
1332    #[test]
1333    fn test_update_mutation_lowers_new_to_existing_upstreams() {
1334        let source = fragment(1);
1335        let mut result = build_status_pair(TestStatus::New, TestStatus::Existing).unwrap();
1336        let source_actor = StreamActor {
1337            actor_id: actor(2),
1338            fragment_id: source,
1339            vnode_bitmap: None,
1340            mview_definition: Default::default(),
1341            expr_context: None,
1342            config_override: Default::default(),
1343        };
1344        let node = StreamNode::default();
1345        result.collect_actors_to_create(std::iter::once((
1346            source,
1347            &node,
1348            std::iter::once((&source_actor, 1.into())),
1349            [],
1350        )));
1351        let mut mutation = UpdateMutation::default();
1352
1353        result.apply_to_update_mutation(&mut mutation);
1354
1355        assert_eq!(mutation.merge_update.len(), 1);
1356        let update = &mutation.merge_update[0];
1357        assert_eq!(update.actor_id, actor(11));
1358        assert_eq!(update.upstream_fragment_id, source);
1359        assert_eq!(
1360            update
1361                .added_upstream_actors
1362                .iter()
1363                .map(|actor| actor.actor_id)
1364                .collect::<Vec<_>>(),
1365            vec![actor(2)]
1366        );
1367        assert!(update.removed_upstream_actor_id.is_empty());
1368        assert_eq!(update.new_upstream_fragment_id, None);
1369    }
1370
1371    #[test]
1372    fn test_attach_new_relation_dispatcher_to_add_mutation() {
1373        let source = fragment(1);
1374        let target = fragment(2);
1375        let mut builder = FragmentEdgeBuilder::new();
1376        builder.add_existing_fragment_infos([(source, single_info(1))]);
1377        builder.add_new_fragment_infos([(target, single_info(11))]);
1378        let (mut result, _) = builder
1379            .finish_fragments()
1380            .add_edge(source, &relation(target, DispatcherType::Broadcast))
1381            .unwrap()
1382            .build();
1383        let target_actor = StreamActor {
1384            actor_id: actor(11),
1385            fragment_id: target,
1386            vnode_bitmap: None,
1387            mview_definition: Default::default(),
1388            expr_context: None,
1389            config_override: Default::default(),
1390        };
1391        let node = StreamNode::default();
1392        result.collect_actors_to_create(std::iter::once((
1393            target,
1394            &node,
1395            std::iter::once((&target_actor, 1.into())),
1396            [],
1397        )));
1398        let mut mutation = AddMutation::default();
1399
1400        result.apply_to_add_mutation(&mut mutation);
1401
1402        assert_eq!(mutation.actor_dispatchers[&actor(1)].dispatchers.len(), 1);
1403        assert_eq!(
1404            mutation.actor_dispatchers[&actor(1)].dispatchers[0].downstream_actor_id,
1405            vec![actor(11)]
1406        );
1407    }
1408
1409    #[test]
1410    fn test_add_relations_only_uses_registered_fragments() {
1411        let external_source = fragment(1);
1412        let source = fragment(2);
1413        let target = fragment(3);
1414        let mut builder = FragmentEdgeBuilder::new();
1415        builder.add_new_fragment_infos([(source, single_info(2)), (target, single_info(3))]);
1416        let (result, _) = builder
1417            .finish_fragments()
1418            .add_relations(&HashMap::from([
1419                (
1420                    external_source,
1421                    vec![relation(source, DispatcherType::Broadcast)],
1422                ),
1423                (source, vec![relation(target, DispatcherType::Broadcast)]),
1424            ]))
1425            .unwrap()
1426            .build();
1427
1428        assert!(!result.dispatchers.contains_key(&external_source));
1429        assert!(!result.upstreams.contains_key(&source));
1430        assert!(result.dispatchers.contains_key(&source));
1431        assert!(result.upstreams.contains_key(&target));
1432    }
1433
1434    #[test]
1435    fn test_add_edge_rejects_unregistered_upstream() {
1436        let source = fragment(1);
1437        let target = fragment(2);
1438        let mut builder = FragmentEdgeBuilder::new();
1439        builder.add_existing_fragment_infos([(target, single_info(2))]);
1440
1441        let result = builder
1442            .finish_fragments()
1443            .add_edge(source, &relation(target, DispatcherType::Broadcast));
1444
1445        assert!(result.is_err());
1446    }
1447
1448    #[test]
1449    #[should_panic(expected = "must use entirely fresh actor ids")]
1450    fn test_changed_fragment_rejects_retained_actor_ids() {
1451        let fragment = fragment(1);
1452        let mut builder = FragmentEdgeBuilder::new();
1453        builder.add_existing_fragment_infos([(fragment, single_info(1))]);
1454        builder.replace_existing_fragment_actor_infos([(fragment, single_info(1))]);
1455    }
1456    #[test]
1457    fn test_attach_new_relation_between_existing_fragments() {
1458        let source = fragment(1);
1459        let target = fragment(2);
1460        let mut builder = FragmentEdgeBuilder::new();
1461        builder.add_existing_fragment_infos([(source, single_info(1)), (target, single_info(11))]);
1462        let mut mutation = UpdateMutation::default();
1463
1464        let (edges, _) = builder
1465            .finish_fragments()
1466            .add_edge(source, &relation(target, DispatcherType::Broadcast))
1467            .unwrap()
1468            .build();
1469        edges.apply_to_update_mutation(&mut mutation);
1470
1471        assert!(mutation.dispatcher_update.is_empty());
1472        assert_eq!(
1473            mutation.actor_new_dispatchers[&actor(1)].dispatchers[0].downstream_actor_id,
1474            vec![actor(11)]
1475        );
1476        assert_eq!(mutation.merge_update.len(), 1);
1477        let merge_update = &mutation.merge_update[0];
1478        assert_eq!(merge_update.actor_id, actor(11));
1479        assert_eq!(merge_update.upstream_fragment_id, source);
1480        assert_eq!(merge_update.added_upstream_actors[0].actor_id, actor(1));
1481        assert!(merge_update.removed_upstream_actor_id.is_empty());
1482    }
1483}