Skip to main content

safe_migrate/analysis/
state.rs

1// FILE: src/analysis/state.rs
2use crate::analysis::facts::{SearchPathTarget, TableConstraintFact};
3use crate::analysis::graph::{
4    DependencyGraph, FkEdge, IndexEdge, PartitionEdge, RenameEdge, SequenceEdge, ViewEdge,
5};
6use crate::analysis::mutations::{
7    AlterTableActionMutation, AlterTypeActionMutation, Mutation, PersistenceMutation,
8};
9use crate::analysis::transaction::{StateChange, TransactionFrame};
10use crate::ast::identifiers::ObjectId;
11use crate::db::cache::DbCache;
12use crate::model::relation::{
13    ColumnAction, Persistence, RelationKind, RelationOverlay, RelationState,
14};
15use crate::model::sequence::{SequenceOverlay, SequenceState};
16use crate::model::types::{TypeKind, TypeOverlay, TypeState};
17use std::collections::{HashMap, HashSet};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum Confidence {
21    Exact,
22    Tainted,
23}
24
25#[derive(Debug, PartialEq, Eq)]
26pub enum MutationResult {
27    Applied,
28    Skipped,
29}
30
31#[derive(Debug, Default, Clone)]
32pub struct CascadeResult {
33    pub dropped_relations: HashSet<ObjectId>,
34    pub dropped_indexes: HashSet<ObjectId>,
35    pub dropped_constraints: HashSet<(ObjectId, String)>,
36}
37
38pub struct LocalState {
39    pub relations: HashMap<ObjectId, RelationOverlay>,
40    pub types: HashMap<ObjectId, TypeOverlay>,
41    pub sequences: HashMap<ObjectId, SequenceOverlay>,
42    pub graph: DependencyGraph,
43    pub search_path: Vec<String>,
44    pub confidence: Confidence,
45    pub transactions: Vec<TransactionFrame>,
46    pub pending_validation: HashSet<(ObjectId, String)>,
47    pub generation_counter: u64,
48}
49
50pub struct AnalysisState {
51    pub pg_version_num: Option<u32>,
52    pub baseline_relations: HashSet<ObjectId>,
53    pub baseline_foreign_keys: HashSet<(ObjectId, String)>,
54    pub local: LocalState,
55}
56
57impl AnalysisState {
58    pub fn new(cache: DbCache) -> Self {
59        let mut relations: HashMap<ObjectId, RelationOverlay> = HashMap::new();
60        let mut baseline_relations = HashSet::new();
61        let mut baseline_foreign_keys = HashSet::new();
62        let mut graph = DependencyGraph::new();
63
64        for (id, rel_state) in cache.baseline_relations() {
65            relations.insert(id.clone(), RelationOverlay::Present(rel_state.clone()));
66            baseline_relations.insert(id.clone());
67        }
68
69        for fk in cache.foreign_keys {
70            baseline_foreign_keys.insert((fk.from_table.clone(), fk.constraint_name.clone()));
71            graph.foreign_keys.push(FkEdge {
72                constraint_name: Some(fk.constraint_name),
73                from_table: fk.from_table,
74                from_columns: Vec::new(),
75                to_table: fk.to_table,
76                to_columns: Vec::new(),
77                from_generation: 0,
78            });
79        }
80
81        for idx in cache.indexes {
82            baseline_relations.insert(idx.index_id.clone());
83            graph.indexes.push(IndexEdge {
84                index_id: idx.index_id,
85                relation_id: idx.table_id,
86                using_method: None,
87                has_predicate: false,
88                is_concurrent: false,
89            });
90        }
91
92        Self {
93            pg_version_num: cache.pg_version_num,
94            baseline_relations,
95            baseline_foreign_keys,
96            local: LocalState {
97                relations,
98                types: HashMap::new(),
99                sequences: HashMap::new(),
100                graph,
101                search_path: vec!["public".to_string()],
102                confidence: Confidence::Exact,
103                transactions: Vec::new(),
104                pending_validation: HashSet::new(),
105                generation_counter: 0,
106            },
107        }
108    }
109
110    pub fn get_relation(&self, id: &ObjectId) -> Option<&RelationOverlay> {
111        self.local.relations.get(id)
112    }
113
114    pub fn relation_is_present(&self, id: &ObjectId) -> bool {
115        matches!(
116            self.local.relations.get(id),
117            Some(RelationOverlay::Present(_))
118        )
119    }
120
121    pub fn get_cascade_closure(&self, target_oid: &ObjectId) -> CascadeResult {
122        let mut result = CascadeResult::default();
123        let mut visited = HashSet::new();
124        self.walk_cascade(target_oid, &mut visited, &mut result);
125        result
126    }
127
128    fn walk_cascade(
129        &self,
130        current: &ObjectId,
131        visited: &mut HashSet<ObjectId>,
132        result: &mut CascadeResult,
133    ) {
134        let resolved_current = self.local.graph.resolve_rename(current).clone();
135
136        if !visited.insert(resolved_current.clone()) {
137            return;
138        }
139
140        result.dropped_relations.insert(resolved_current.clone());
141
142        for view_edge in &self.local.graph.views {
143            if view_edge
144                .depends_on
145                .iter()
146                .any(|dep| self.local.graph.resolve_rename(dep) == &resolved_current)
147            {
148                let resolved_view_id = self.local.graph.resolve_rename(&view_edge.view_id).clone();
149                if !visited.contains(&resolved_view_id) {
150                    self.walk_cascade(&resolved_view_id, visited, result);
151                }
152            }
153        }
154
155        for index_edge in &self.local.graph.indexes {
156            if self.local.graph.resolve_rename(&index_edge.relation_id) == &resolved_current {
157                result.dropped_indexes.insert(
158                    self.local
159                        .graph
160                        .resolve_rename(&index_edge.index_id)
161                        .clone(),
162                );
163            }
164        }
165
166        for fk_edge in &self.local.graph.foreign_keys {
167            if self.local.graph.resolve_rename(&fk_edge.to_table) == &resolved_current
168                && let Some(cname) = &fk_edge.constraint_name
169            {
170                result.dropped_constraints.insert((
171                    self.local.graph.resolve_rename(&fk_edge.from_table).clone(),
172                    cname.clone(),
173                ));
174            }
175        }
176
177        for partition_edge in &self.local.graph.partitions {
178            if self.local.graph.resolve_rename(&partition_edge.parent) == &resolved_current {
179                let resolved_child = self
180                    .local
181                    .graph
182                    .resolve_rename(&partition_edge.child)
183                    .clone();
184                if !visited.contains(&resolved_child) {
185                    self.walk_cascade(&resolved_child, visited, result);
186                }
187            }
188        }
189    }
190
191    pub fn apply(
192        &mut self,
193        mutation: &Mutation,
194        precomputed_cascade: Option<&CascadeResult>,
195    ) -> MutationResult {
196        match mutation {
197            Mutation::CreateSchema(_) => MutationResult::Applied,
198            Mutation::DropSchema(drop_schema) => {
199                if drop_schema.cascade {
200                    let renames = self.local.graph.renames.clone();
201                    let resolve = |id: &ObjectId| -> ObjectId {
202                        let mut current = id;
203                        loop {
204                            match renames.iter().find(|r| &r.from == current) {
205                                Some(edge) => current = &edge.to,
206                                None => return current.clone(),
207                            }
208                        }
209                    };
210
211                    let mut relations_to_drop = Vec::new();
212                    for id in self.local.relations.keys() {
213                        if drop_schema.names.contains(&id.schema) {
214                            relations_to_drop.push(id.clone());
215                        }
216                    }
217                    for id in relations_to_drop {
218                        self.snapshot_relation(&id);
219                        self.local.relations.insert(id, RelationOverlay::Dropped);
220                    }
221
222                    let mut types_to_drop = Vec::new();
223                    for id in self.local.types.keys() {
224                        if drop_schema.names.contains(&id.schema) {
225                            types_to_drop.push(id.clone());
226                        }
227                    }
228                    for id in types_to_drop {
229                        self.snapshot_type(&id);
230                        self.local.types.insert(id, TypeOverlay::Dropped);
231                    }
232
233                    let mut seqs_to_drop = Vec::new();
234                    for id in self.local.sequences.keys() {
235                        if drop_schema.names.contains(&id.schema) {
236                            seqs_to_drop.push(id.clone());
237                        }
238                    }
239                    for id in seqs_to_drop {
240                        self.snapshot_sequence(&id);
241                        self.local.sequences.insert(id, SequenceOverlay::Dropped);
242                    }
243
244                    self.snapshot_fk_graph_full();
245                    self.snapshot_view_graph_full();
246                    self.snapshot_index_graph_full();
247                    self.snapshot_partition_graph_full();
248                    self.snapshot_sequence_graph_full();
249                    self.snapshot_rename_graph_full();
250
251                    let g = &mut self.local.graph;
252                    g.foreign_keys.retain(|fk| {
253                        !drop_schema.names.contains(&resolve(&fk.from_table).schema)
254                            && !drop_schema.names.contains(&resolve(&fk.to_table).schema)
255                    });
256                    g.views
257                        .retain(|v| !drop_schema.names.contains(&resolve(&v.view_id).schema));
258                    g.indexes
259                        .retain(|idx| !drop_schema.names.contains(&resolve(&idx.index_id).schema));
260                    g.partitions.retain(|p| {
261                        !drop_schema.names.contains(&resolve(&p.parent).schema)
262                            && !drop_schema.names.contains(&resolve(&p.child).schema)
263                    });
264                    g.sequences
265                        .retain(|s| !drop_schema.names.contains(&resolve(&s.sequence_id).schema));
266                    g.renames.retain(|r| {
267                        !drop_schema.names.contains(&resolve(&r.from).schema)
268                            && !drop_schema.names.contains(&resolve(&r.to).schema)
269                    });
270                }
271                MutationResult::Applied
272            }
273            Mutation::DropTable(drop_table) => {
274                if !self.relation_is_present(&drop_table.id) {
275                    if drop_table.if_exists {
276                        return MutationResult::Skipped;
277                    } else {
278                        self.local.confidence = Confidence::Tainted;
279                        return MutationResult::Skipped;
280                    }
281                }
282
283                let renames = self.local.graph.renames.clone();
284                let resolve = |id: &ObjectId| -> ObjectId {
285                    let mut current = id;
286                    loop {
287                        match renames.iter().find(|r| &r.from == current) {
288                            Some(edge) => current = &edge.to,
289                            None => return current.clone(),
290                        }
291                    }
292                };
293
294                let resolved_drop = resolve(&drop_table.id);
295
296                if drop_table.cascade {
297                    let local_closure;
298                    let closure = match precomputed_cascade {
299                        Some(c) => c,
300                        None => {
301                            local_closure = self.get_cascade_closure(&drop_table.id);
302                            &local_closure
303                        }
304                    };
305
306                    for dropped_rel_id in &closure.dropped_relations {
307                        self.snapshot_relation(dropped_rel_id);
308                        self.local
309                            .relations
310                            .insert(dropped_rel_id.clone(), RelationOverlay::Dropped);
311                    }
312
313                    self.snapshot_index_graph_full();
314                    self.local
315                        .graph
316                        .indexes
317                        .retain(|idx| !closure.dropped_indexes.contains(&resolve(&idx.index_id)));
318
319                    self.snapshot_fk_graph_full();
320                    self.local.graph.foreign_keys.retain(|fk| {
321                        let from_dropped =
322                            closure.dropped_relations.contains(&resolve(&fk.from_table));
323                        let to_dropped = closure.dropped_relations.contains(&resolve(&fk.to_table));
324                        let constraint_explicitly_dropped = if let Some(cname) = &fk.constraint_name
325                        {
326                            closure
327                                .dropped_constraints
328                                .contains(&(resolve(&fk.from_table), cname.clone()))
329                        } else {
330                            false
331                        };
332                        !(from_dropped || to_dropped || constraint_explicitly_dropped)
333                    });
334
335                    self.snapshot_view_graph_full();
336                    self.local
337                        .graph
338                        .views
339                        .retain(|v| !closure.dropped_relations.contains(&resolve(&v.view_id)));
340                } else {
341                    let has_view_deps = self
342                        .local
343                        .graph
344                        .views
345                        .iter()
346                        .any(|v| v.depends_on.iter().any(|dep| resolve(dep) == resolved_drop));
347                    let has_fk_deps = self.local.graph.foreign_keys.iter().any(|fk| {
348                        resolve(&fk.to_table) == resolved_drop
349                            && resolve(&fk.from_table) != resolved_drop
350                    });
351                    let has_partition_deps = self
352                        .local
353                        .graph
354                        .partitions
355                        .iter()
356                        .any(|p| resolve(&p.parent) == resolved_drop);
357
358                    if has_view_deps || has_fk_deps || has_partition_deps {
359                        self.local.confidence = Confidence::Tainted;
360                        return MutationResult::Skipped;
361                    }
362
363                    self.snapshot_relation(&drop_table.id);
364                    self.local
365                        .relations
366                        .insert(drop_table.id.clone(), RelationOverlay::Dropped);
367                }
368
369                self.snapshot_partition_graph_full();
370                self.local.graph.partitions.retain(|p| {
371                    resolve(&p.parent) != resolved_drop && resolve(&p.child) != resolved_drop
372                });
373
374                MutationResult::Applied
375            }
376            Mutation::CreateTable(create) => {
377                if create.if_not_exists && self.relation_is_present(&create.id) {
378                    return MutationResult::Skipped;
379                }
380
381                self.snapshot_relation(&create.id);
382
383                self.snapshot_generation_counter();
384                self.local.generation_counter += 1;
385                let generation = self.local.generation_counter;
386                let tx_depth = self.local.transactions.len();
387
388                let pk_columns: HashSet<&str> = create
389                    .table_constraints
390                    .iter()
391                    .filter_map(|tc| {
392                        if let TableConstraintFact::PrimaryKey { columns } = tc {
393                            Some(columns.iter().map(|s| s.as_str()))
394                        } else {
395                            None
396                        }
397                    })
398                    .flatten()
399                    .collect();
400
401                let estimated_rows = if create.as_select { None } else { Some(0) };
402
403                let persistence = match &create.persistence {
404                    PersistenceMutation::Permanent => Persistence::Permanent,
405                    PersistenceMutation::Temporary => Persistence::Temporary,
406                    PersistenceMutation::Unlogged => Persistence::Unlogged,
407                };
408
409                let mut rel_state = RelationState::new(
410                    create.id.clone(),
411                    generation,
412                    estimated_rows,
413                    RelationKind::Table,
414                    persistence,
415                    tx_depth,
416                );
417
418                for col in &create.columns {
419                    let is_pk = col.is_primary_key || pk_columns.contains(col.name.as_str());
420                    rel_state.apply_column_action(&ColumnAction::Add {
421                        name: col.name.clone(),
422                        data_type: col.ty.clone(),
423                        not_null: col.not_null || is_pk,
424                        default: col.default.clone(),
425                    });
426                }
427
428                self.local
429                    .relations
430                    .insert(create.id.clone(), RelationOverlay::Present(rel_state));
431
432                if let Some(parent_id) = &create.partition_of {
433                    self.snapshot_partition_graph();
434                    self.local.graph.partitions.push(PartitionEdge {
435                        parent: parent_id.clone(),
436                        child: create.id.clone(),
437                    });
438                }
439
440                if !create.foreign_keys.is_empty() {
441                    self.snapshot_fk_graph();
442                }
443
444                for fk in &create.foreign_keys {
445                    self.local.graph.foreign_keys.push(FkEdge {
446                        constraint_name: fk.constraint_name.clone(),
447                        from_table: create.id.clone(),
448                        from_columns: fk.from_columns.clone(),
449                        to_table: fk.to_table.clone(),
450                        to_columns: fk.to_columns.clone(),
451                        from_generation: generation,
452                    });
453                }
454                MutationResult::Applied
455            }
456            Mutation::CreateView(create_view) => {
457                self.snapshot_relation(&create_view.id);
458                self.snapshot_view_graph();
459                self.snapshot_generation_counter();
460                self.local.generation_counter += 1;
461                let generation = self.local.generation_counter;
462                let tx_depth = self.local.transactions.len();
463
464                self.local.relations.insert(
465                    create_view.id.clone(),
466                    RelationOverlay::Present(RelationState::new(
467                        create_view.id.clone(),
468                        generation,
469                        Some(0),
470                        RelationKind::View,
471                        Persistence::Permanent,
472                        tx_depth,
473                    )),
474                );
475
476                self.local.graph.views.push(ViewEdge {
477                    view_id: create_view.id.clone(),
478                    depends_on: create_view.depends_on.clone(),
479                    view_generation: generation,
480                });
481                MutationResult::Applied
482            }
483            Mutation::CreateMaterializedView(create_mat) => {
484                self.snapshot_relation(&create_mat.id);
485                self.snapshot_view_graph();
486                self.snapshot_generation_counter();
487                self.local.generation_counter += 1;
488                let generation = self.local.generation_counter;
489                let tx_depth = self.local.transactions.len();
490
491                self.local.relations.insert(
492                    create_mat.id.clone(),
493                    RelationOverlay::Present(RelationState::new(
494                        create_mat.id.clone(),
495                        generation,
496                        None,
497                        RelationKind::MaterializedView,
498                        Persistence::Permanent,
499                        tx_depth,
500                    )),
501                );
502
503                self.local.graph.views.push(ViewEdge {
504                    view_id: create_mat.id.clone(),
505                    depends_on: create_mat.depends_on.clone(),
506                    view_generation: generation,
507                });
508                MutationResult::Applied
509            }
510            Mutation::RefreshMaterializedView(refresh) => {
511                if !self.relation_is_present(&refresh.id) {
512                    self.local.confidence = Confidence::Tainted;
513                    return MutationResult::Skipped;
514                }
515
516                self.snapshot_relation(&refresh.id);
517                self.snapshot_generation_counter();
518                self.local.generation_counter += 1;
519                let new_gen = self.local.generation_counter;
520
521                if let Some(RelationOverlay::Present(rel)) =
522                    self.local.relations.get_mut(&refresh.id)
523                {
524                    rel.generation = new_gen;
525                }
526                MutationResult::Applied
527            }
528            Mutation::CreateIndex(create_index) => {
529                if create_index.if_not_exists
530                    && self
531                        .local
532                        .graph
533                        .indexes
534                        .iter()
535                        .any(|idx| idx.index_id == create_index.id)
536                {
537                    return MutationResult::Skipped;
538                }
539                self.snapshot_index_graph();
540                self.local.graph.indexes.push(IndexEdge {
541                    index_id: create_index.id.clone(),
542                    relation_id: create_index.table.clone(),
543                    using_method: create_index.using_method.clone(),
544                    has_predicate: create_index.has_predicate,
545                    is_concurrent: create_index.concurrently,
546                });
547                MutationResult::Applied
548            }
549            Mutation::CreatePolicy(policy) => {
550                self.snapshot_relation(&policy.table);
551                self.snapshot_generation_counter();
552                self.local.generation_counter += 1;
553                let new_gen = self.local.generation_counter;
554
555                if let Some(RelationOverlay::Present(rel)) =
556                    self.local.relations.get_mut(&policy.table)
557                {
558                    rel.policies.insert(policy.name.clone());
559                    rel.generation = new_gen;
560                }
561                MutationResult::Applied
562            }
563            Mutation::DropPolicy(policy) => {
564                if policy.if_exists {
565                    let exists = self
566                        .local
567                        .relations
568                        .get(&policy.table)
569                        .map(|o| {
570                            if let RelationOverlay::Present(rel) = o {
571                                rel.policies.contains(&policy.name)
572                            } else {
573                                false
574                            }
575                        })
576                        .unwrap_or(false);
577                    if !exists {
578                        return MutationResult::Skipped;
579                    }
580                }
581                self.snapshot_relation(&policy.table);
582                self.snapshot_generation_counter();
583                self.local.generation_counter += 1;
584                let new_gen = self.local.generation_counter;
585
586                if let Some(RelationOverlay::Present(rel)) =
587                    self.local.relations.get_mut(&policy.table)
588                {
589                    rel.policies.remove(&policy.name);
590                    rel.generation = new_gen;
591                }
592                MutationResult::Applied
593            }
594            Mutation::CreateTrigger(trigger) => {
595                self.snapshot_relation(&trigger.table);
596                self.snapshot_generation_counter();
597                self.local.generation_counter += 1;
598                let new_gen = self.local.generation_counter;
599
600                if let Some(RelationOverlay::Present(rel)) =
601                    self.local.relations.get_mut(&trigger.table)
602                {
603                    rel.triggers.insert(trigger.name.clone());
604                    rel.generation = new_gen;
605                }
606                MutationResult::Applied
607            }
608            Mutation::DropTrigger(trigger) => {
609                if trigger.if_exists {
610                    let exists = self
611                        .local
612                        .relations
613                        .get(&trigger.table)
614                        .map(|o| {
615                            if let RelationOverlay::Present(rel) = o {
616                                rel.triggers.contains(&trigger.name)
617                            } else {
618                                false
619                            }
620                        })
621                        .unwrap_or(false);
622                    if !exists {
623                        return MutationResult::Skipped;
624                    }
625                }
626                self.snapshot_relation(&trigger.table);
627                self.snapshot_generation_counter();
628                self.local.generation_counter += 1;
629                let new_gen = self.local.generation_counter;
630
631                if let Some(RelationOverlay::Present(rel)) =
632                    self.local.relations.get_mut(&trigger.table)
633                {
634                    rel.triggers.remove(&trigger.name);
635                    rel.generation = new_gen;
636                }
637                MutationResult::Applied
638            }
639            Mutation::CreateType(create_type) => {
640                self.snapshot_type(&create_type.id);
641                self.snapshot_generation_counter();
642                self.local.generation_counter += 1;
643                let generation = self.local.generation_counter;
644
645                self.local.types.insert(
646                    create_type.id.clone(),
647                    TypeOverlay::Present(TypeState {
648                        id: create_type.id.clone(),
649                        generation,
650                        kind: create_type.kind.clone(),
651                    }),
652                );
653                MutationResult::Applied
654            }
655            Mutation::AlterType(alter) => {
656                self.snapshot_type(&alter.id);
657                if let Some(TypeOverlay::Present(t)) = self.local.types.get_mut(&alter.id) {
658                    match &alter.action {
659                        AlterTypeActionMutation::AddValue { new_value } => {
660                            if let TypeKind::Enum { variants } = &mut t.kind {
661                                variants.push(new_value.clone());
662                            }
663                        }
664                    }
665                }
666                MutationResult::Applied
667            }
668            Mutation::CreateDomain(create) => {
669                self.snapshot_type(&create.id);
670                self.snapshot_generation_counter();
671                self.local.generation_counter += 1;
672                let generation = self.local.generation_counter;
673
674                self.local.types.insert(
675                    create.id.clone(),
676                    TypeOverlay::Present(TypeState {
677                        id: create.id.clone(),
678                        generation,
679                        kind: TypeKind::Domain {
680                            base_type: create.base_type.clone(),
681                        },
682                    }),
683                );
684                MutationResult::Applied
685            }
686            Mutation::AlterDomain(alter) => {
687                self.snapshot_type(&alter.id);
688                self.snapshot_generation_counter();
689                self.local.generation_counter += 1;
690                let new_gen = self.local.generation_counter;
691
692                if let Some(TypeOverlay::Present(t)) = self.local.types.get_mut(&alter.id) {
693                    t.generation = new_gen;
694                }
695                MutationResult::Applied
696            }
697            Mutation::DropDomain(drop_domain) => {
698                for id in &drop_domain.ids {
699                    if !self.local.types.contains_key(id) && !drop_domain.if_exists {
700                        self.local.confidence = Confidence::Tainted;
701                        return MutationResult::Skipped;
702                    }
703                }
704                let mut any_applied = false;
705                for id in &drop_domain.ids {
706                    if !self.local.types.contains_key(id) {
707                        continue;
708                    }
709                    self.snapshot_type(id);
710                    self.local.types.insert(id.clone(), TypeOverlay::Dropped);
711                    any_applied = true;
712                }
713                if !any_applied && !drop_domain.ids.is_empty() {
714                    return MutationResult::Skipped;
715                }
716                MutationResult::Applied
717            }
718            Mutation::CreateSequence(create) => {
719                if create.if_not_exists && self.local.sequences.contains_key(&create.id) {
720                    return MutationResult::Skipped;
721                }
722                self.snapshot_sequence(&create.id);
723                self.snapshot_generation_counter();
724                self.local.generation_counter += 1;
725                let generation = self.local.generation_counter;
726
727                self.local.sequences.insert(
728                    create.id.clone(),
729                    SequenceOverlay::Present(SequenceState {
730                        id: create.id.clone(),
731                        generation,
732                    }),
733                );
734
735                if let Some((table_id, col)) = &create.owned_by {
736                    self.snapshot_sequence_graph();
737                    self.local.graph.sequences.push(SequenceEdge {
738                        sequence_id: create.id.clone(),
739                        table_id: table_id.clone(),
740                        column: col.clone(),
741                    });
742                }
743                MutationResult::Applied
744            }
745            Mutation::AlterSequence(alter) => {
746                self.snapshot_sequence(&alter.id);
747                self.snapshot_generation_counter();
748                self.local.generation_counter += 1;
749
750                if let Some((table_id, col)) = &alter.owned_by {
751                    self.snapshot_sequence_graph_full();
752                    self.local
753                        .graph
754                        .sequences
755                        .retain(|s| s.sequence_id != alter.id);
756                    self.local.graph.sequences.push(SequenceEdge {
757                        sequence_id: alter.id.clone(),
758                        table_id: table_id.clone(),
759                        column: col.clone(),
760                    });
761                }
762                MutationResult::Applied
763            }
764            Mutation::DropSequence(drop_seq) => {
765                for id in &drop_seq.ids {
766                    if !self.local.sequences.contains_key(id) && !drop_seq.if_exists {
767                        self.local.confidence = Confidence::Tainted;
768                        return MutationResult::Skipped;
769                    }
770                }
771                let mut any_applied = false;
772                for id in &drop_seq.ids {
773                    if !self.local.sequences.contains_key(id) {
774                        continue;
775                    }
776                    self.snapshot_sequence(id);
777                    self.local
778                        .sequences
779                        .insert(id.clone(), SequenceOverlay::Dropped);
780                    self.snapshot_sequence_graph_full();
781                    self.local.graph.sequences.retain(|s| s.sequence_id != *id);
782                    any_applied = true;
783                }
784                if !any_applied && !drop_seq.ids.is_empty() {
785                    return MutationResult::Skipped;
786                }
787                MutationResult::Applied
788            }
789            Mutation::AlterTable(alter) => {
790                if !self.relation_is_present(&alter.id) {
791                    self.local.confidence = Confidence::Tainted;
792                    return MutationResult::Skipped;
793                }
794
795                match &alter.action {
796                    AlterTableActionMutation::AddColumn {
797                        name,
798                        if_not_exists,
799                        ..
800                    } if *if_not_exists => {
801                        if let Some(RelationOverlay::Present(rel)) =
802                            self.local.relations.get(&alter.id)
803                            && rel.has_column(name)
804                        {
805                            return MutationResult::Skipped;
806                        }
807                    }
808                    AlterTableActionMutation::DropColumn {
809                        name, if_exists, ..
810                    } if *if_exists => {
811                        if let Some(RelationOverlay::Present(rel)) =
812                            self.local.relations.get(&alter.id)
813                            && !rel.has_column(name)
814                        {
815                            return MutationResult::Skipped;
816                        }
817                    }
818                    _ => {}
819                }
820
821                self.snapshot_relation(&alter.id);
822
823                match &alter.action {
824                    AlterTableActionMutation::AddColumn {
825                        name,
826                        ty,
827                        not_null,
828                        default,
829                        ..
830                    } => {
831                        if let Some(RelationOverlay::Present(rel)) =
832                            self.local.relations.get_mut(&alter.id)
833                        {
834                            rel.apply_column_action(&ColumnAction::Add {
835                                name: name.clone(),
836                                data_type: ty.clone(),
837                                not_null: *not_null,
838                                default: default.clone(),
839                            });
840                        }
841                    }
842                    AlterTableActionMutation::DropColumn { name, .. } => {
843                        if let Some(RelationOverlay::Present(rel)) =
844                            self.local.relations.get_mut(&alter.id)
845                        {
846                            rel.apply_column_action(&ColumnAction::Drop { name: name.clone() });
847                        }
848                    }
849                    AlterTableActionMutation::RenameColumn { from, to } => {
850                        if let Some(RelationOverlay::Present(rel)) =
851                            self.local.relations.get_mut(&alter.id)
852                        {
853                            rel.apply_column_action(&ColumnAction::Rename {
854                                from: from.clone(),
855                                to: to.clone(),
856                            });
857                        }
858                    }
859                    AlterTableActionMutation::SetNotNull { column } => {
860                        if let Some(RelationOverlay::Present(rel)) =
861                            self.local.relations.get_mut(&alter.id)
862                        {
863                            rel.apply_column_action(&ColumnAction::SetNotNull {
864                                name: column.clone(),
865                            });
866                        }
867                    }
868                    AlterTableActionMutation::DropNotNull { column } => {
869                        if let Some(RelationOverlay::Present(rel)) =
870                            self.local.relations.get_mut(&alter.id)
871                        {
872                            rel.apply_column_action(&ColumnAction::DropNotNull {
873                                name: column.clone(),
874                            });
875                        }
876                    }
877                    AlterTableActionMutation::SetType {
878                        column,
879                        ty,
880                        has_using: _,
881                    } => {
882                        if let Some(RelationOverlay::Present(rel)) =
883                            self.local.relations.get_mut(&alter.id)
884                        {
885                            rel.apply_column_action(&ColumnAction::SetType {
886                                name: column.clone(),
887                                data_type: ty.clone(),
888                            });
889                        }
890                    }
891                    AlterTableActionMutation::SetDefault { column, default } => {
892                        if let Some(RelationOverlay::Present(rel)) =
893                            self.local.relations.get_mut(&alter.id)
894                        {
895                            rel.apply_column_action(&ColumnAction::SetDefault {
896                                name: column.clone(),
897                                default: default.clone(),
898                            });
899                        }
900                    }
901                    AlterTableActionMutation::AddForeignKey {
902                        constraint_name,
903                        to_table,
904                        from_columns,
905                        to_columns,
906                        not_valid,
907                    } => {
908                        self.snapshot_fk_graph();
909                        let from_generation = self
910                            .local
911                            .relations
912                            .get(&alter.id)
913                            .and_then(|o| {
914                                if let RelationOverlay::Present(r) = o {
915                                    Some(r.generation)
916                                } else {
917                                    None
918                                }
919                            })
920                            .unwrap_or(0);
921
922                        self.local.graph.foreign_keys.push(FkEdge {
923                            constraint_name: constraint_name.clone(),
924                            from_table: alter.id.clone(),
925                            from_columns: from_columns.clone(),
926                            to_table: to_table.clone(),
927                            to_columns: to_columns.clone(),
928                            from_generation,
929                        });
930
931                        if *not_valid {
932                            let key = constraint_name
933                                .clone()
934                                .unwrap_or_else(|| format!("__fk__{}", to_table));
935                            self.snapshot_pending_validation();
936                            self.local
937                                .pending_validation
938                                .insert((alter.id.clone(), key));
939                        }
940                    }
941                    AlterTableActionMutation::RenameConstraint { old_name, new_name } => {
942                        self.snapshot_fk_graph_full();
943                        for fk in &mut self.local.graph.foreign_keys {
944                            if fk.from_table == alter.id
945                                && fk.constraint_name.as_deref() == Some(old_name.as_str())
946                            {
947                                fk.constraint_name = Some(new_name.clone());
948                            }
949                        }
950                    }
951                    AlterTableActionMutation::DropConstraint { name } => {
952                        self.snapshot_pending_validation();
953                        self.local
954                            .pending_validation
955                            .remove(&(alter.id.clone(), name.clone()));
956                        self.snapshot_fk_graph_full();
957                        self.local
958                            .graph
959                            .foreign_keys
960                            .retain(|fk| fk.constraint_name.as_deref() != Some(name.as_str()));
961                    }
962                    AlterTableActionMutation::ValidateConstraint { constraint_name } => {
963                        self.snapshot_pending_validation();
964                        self.local
965                            .pending_validation
966                            .remove(&(alter.id.clone(), constraint_name.clone()));
967                    }
968                    AlterTableActionMutation::AttachPartition { child } => {
969                        self.snapshot_partition_graph();
970                        self.local.graph.partitions.push(PartitionEdge {
971                            parent: alter.id.clone(),
972                            child: child.clone(),
973                        });
974                    }
975                    AlterTableActionMutation::DetachPartition { child } => {
976                        self.snapshot_partition_graph_full();
977                        self.local
978                            .graph
979                            .partitions
980                            .retain(|p| !(p.parent == alter.id && p.child == *child));
981                    }
982                    AlterTableActionMutation::AlterConstraint { .. }
983                    | AlterTableActionMutation::AddCheckConstraint { .. }
984                    | AlterTableActionMutation::AddUniqueConstraint
985                    | AlterTableActionMutation::AddPrimaryKeyConstraint
986                    | AlterTableActionMutation::AddExcludeConstraint
987                    | AlterTableActionMutation::SetStorage { .. }
988                    | AlterTableActionMutation::SetAccessMethod => {
989                        // Tracking only
990                    }
991                }
992                MutationResult::Applied
993            }
994            Mutation::Rename(rename) => {
995                self.snapshot_relation(&rename.old_id);
996                self.snapshot_relation(&rename.new_id);
997                self.snapshot_rename_graph();
998                if let Some(overlay) = self.local.relations.remove(&rename.old_id) {
999                    self.local.relations.insert(rename.new_id.clone(), overlay);
1000                }
1001
1002                self.snapshot_index_graph_full();
1003                for idx in &mut self.local.graph.indexes {
1004                    if idx.index_id == rename.old_id {
1005                        idx.index_id = rename.new_id.clone();
1006                    }
1007                }
1008
1009                self.snapshot_sequence(&rename.old_id);
1010                self.snapshot_sequence(&rename.new_id);
1011                if let Some(overlay) = self.local.sequences.remove(&rename.old_id) {
1012                    self.local.sequences.insert(rename.new_id.clone(), overlay);
1013                }
1014
1015                self.snapshot_type(&rename.old_id);
1016                self.snapshot_type(&rename.new_id);
1017                if let Some(overlay) = self.local.types.remove(&rename.old_id) {
1018                    self.local.types.insert(rename.new_id.clone(), overlay);
1019                }
1020
1021                self.local.graph.renames.push(RenameEdge {
1022                    from: rename.old_id.clone(),
1023                    to: rename.new_id.clone(),
1024                });
1025                MutationResult::Applied
1026            }
1027            Mutation::DropView(drop_view) => {
1028                for id in &drop_view.ids {
1029                    if !self.relation_is_present(id) && !drop_view.if_exists {
1030                        self.local.confidence = Confidence::Tainted;
1031                        return MutationResult::Skipped;
1032                    }
1033                }
1034
1035                let renames = self.local.graph.renames.clone();
1036                let resolve = |id: &ObjectId| -> ObjectId {
1037                    let mut current = id;
1038                    loop {
1039                        match renames.iter().find(|r| &r.from == current) {
1040                            Some(edge) => current = &edge.to,
1041                            None => return current.clone(),
1042                        }
1043                    }
1044                };
1045
1046                let mut any_applied = false;
1047                for id in &drop_view.ids {
1048                    if !self.relation_is_present(id) {
1049                        continue;
1050                    }
1051
1052                    let resolved_id = resolve(id);
1053                    let has_dependents = self.local.graph.views.iter().any(|v| {
1054                        v.depends_on.iter().any(|dep| resolve(dep) == resolved_id)
1055                            && !drop_view.ids.contains(&resolve(&v.view_id))
1056                    });
1057                    if has_dependents {
1058                        self.local.confidence = Confidence::Tainted;
1059                        return MutationResult::Skipped;
1060                    }
1061
1062                    self.snapshot_relation(id);
1063                    self.local
1064                        .relations
1065                        .insert(id.clone(), RelationOverlay::Dropped);
1066                    self.snapshot_view_graph_full();
1067                    self.local
1068                        .graph
1069                        .views
1070                        .retain(|v| resolve(&v.view_id) != resolved_id);
1071                    any_applied = true;
1072                }
1073                if !any_applied && !drop_view.ids.is_empty() {
1074                    return MutationResult::Skipped;
1075                }
1076                MutationResult::Applied
1077            }
1078            Mutation::DropMaterializedView(drop_mat_view) => {
1079                for id in &drop_mat_view.ids {
1080                    if !self.relation_is_present(id) && !drop_mat_view.if_exists {
1081                        self.local.confidence = Confidence::Tainted;
1082                        return MutationResult::Skipped;
1083                    }
1084                }
1085
1086                let renames = self.local.graph.renames.clone();
1087                let resolve = |id: &ObjectId| -> ObjectId {
1088                    let mut current = id;
1089                    loop {
1090                        match renames.iter().find(|r| &r.from == current) {
1091                            Some(edge) => current = &edge.to,
1092                            None => return current.clone(),
1093                        }
1094                    }
1095                };
1096
1097                let mut any_applied = false;
1098                for id in &drop_mat_view.ids {
1099                    if !self.relation_is_present(id) {
1100                        continue;
1101                    }
1102
1103                    let resolved_id = resolve(id);
1104                    let has_dependents = self.local.graph.views.iter().any(|v| {
1105                        v.depends_on.iter().any(|dep| resolve(dep) == resolved_id)
1106                            && !drop_mat_view.ids.contains(&resolve(&v.view_id))
1107                    });
1108                    if has_dependents {
1109                        self.local.confidence = Confidence::Tainted;
1110                        return MutationResult::Skipped;
1111                    }
1112
1113                    self.snapshot_relation(id);
1114                    self.local
1115                        .relations
1116                        .insert(id.clone(), RelationOverlay::Dropped);
1117                    self.snapshot_view_graph_full();
1118                    self.local
1119                        .graph
1120                        .views
1121                        .retain(|v| resolve(&v.view_id) != resolved_id);
1122                    any_applied = true;
1123                }
1124                if !any_applied && !drop_mat_view.ids.is_empty() {
1125                    return MutationResult::Skipped;
1126                }
1127                MutationResult::Applied
1128            }
1129            Mutation::DropIndex(drop_index) => {
1130                let present = self
1131                    .local
1132                    .graph
1133                    .indexes
1134                    .iter()
1135                    .any(|idx| idx.index_id == drop_index.id);
1136                if !present {
1137                    if drop_index.if_exists {
1138                        return MutationResult::Skipped;
1139                    } else {
1140                        self.local.confidence = Confidence::Tainted;
1141                        return MutationResult::Skipped;
1142                    }
1143                }
1144                self.snapshot_index_graph_full();
1145                self.local
1146                    .graph
1147                    .indexes
1148                    .retain(|idx| idx.index_id != drop_index.id);
1149                MutationResult::Applied
1150            }
1151            Mutation::SearchPath(change) => {
1152                self.snapshot_search_path();
1153                match &change.target {
1154                    SearchPathTarget::Default => {
1155                        self.local.search_path = vec!["public".to_string()];
1156                    }
1157                    SearchPathTarget::Schemas(schemas) => {
1158                        self.local.search_path = schemas.clone();
1159                    }
1160                }
1161                MutationResult::Applied
1162            }
1163            Mutation::BeginTransaction => {
1164                self.local
1165                    .transactions
1166                    .push(TransactionFrame::new("__transaction__"));
1167                MutationResult::Applied
1168            }
1169            Mutation::CommitTransaction => {
1170                self.local.transactions.clear();
1171                MutationResult::Applied
1172            }
1173            Mutation::RollbackTransaction => {
1174                let frames: Vec<_> = self.local.transactions.drain(..).collect();
1175                for frame in frames.into_iter().rev() {
1176                    self.replay_undo_log(frame);
1177                }
1178                MutationResult::Applied
1179            }
1180            Mutation::Savepoint(sp) => {
1181                self.local
1182                    .transactions
1183                    .push(TransactionFrame::new(sp.name.clone()));
1184                MutationResult::Applied
1185            }
1186            Mutation::ReleaseSavepoint(rsp) => {
1187                if let Some(pos) = self
1188                    .local
1189                    .transactions
1190                    .iter()
1191                    .rposition(|f| f.name == rsp.name)
1192                {
1193                    let mut released_frame = self.local.transactions.remove(pos);
1194                    if let Some(parent_frame) = self.local.transactions.last_mut() {
1195                        parent_frame.undo_log.append(&mut released_frame.undo_log);
1196                    }
1197                }
1198                MutationResult::Applied
1199            }
1200            Mutation::RollbackToSavepoint(rsp) => {
1201                if let Some(pos) = self
1202                    .local
1203                    .transactions
1204                    .iter()
1205                    .rposition(|f| f.name == rsp.name)
1206                {
1207                    let frames: Vec<_> = self.local.transactions.drain(pos..).collect();
1208                    for frame in frames.into_iter().rev() {
1209                        self.replay_undo_log(frame);
1210                    }
1211                    self.local
1212                        .transactions
1213                        .push(TransactionFrame::new(rsp.name.clone()));
1214                }
1215                MutationResult::Applied
1216            }
1217            Mutation::Opaque(_) => {
1218                self.local.confidence = Confidence::Tainted;
1219                MutationResult::Applied
1220            }
1221            Mutation::Vacuum { .. } => MutationResult::Applied,
1222        }
1223    }
1224
1225    fn snapshot_relation(&mut self, id: &ObjectId) {
1226        if let Some(frame) = self.local.transactions.last_mut() {
1227            let previous = self.local.relations.get(id).cloned();
1228            frame.undo_log.push(StateChange::RelationSnapshot {
1229                id: id.clone(),
1230                previous,
1231            });
1232        }
1233    }
1234
1235    fn snapshot_type(&mut self, id: &ObjectId) {
1236        if let Some(frame) = self.local.transactions.last_mut() {
1237            let previous = self.local.types.get(id).cloned();
1238            frame.undo_log.push(StateChange::TypeSnapshot {
1239                id: id.clone(),
1240                previous,
1241            });
1242        }
1243    }
1244
1245    fn snapshot_sequence(&mut self, id: &ObjectId) {
1246        if let Some(frame) = self.local.transactions.last_mut() {
1247            let previous = self.local.sequences.get(id).cloned();
1248            frame.undo_log.push(StateChange::SequenceSnapshot {
1249                id: id.clone(),
1250                previous,
1251            });
1252        }
1253    }
1254
1255    fn snapshot_fk_graph(&mut self) {
1256        if let Some(frame) = self.local.transactions.last_mut() {
1257            frame.undo_log.push(StateChange::FkGraphLengthMarker {
1258                len: self.local.graph.foreign_keys.len(),
1259            });
1260        }
1261    }
1262
1263    fn snapshot_fk_graph_full(&mut self) {
1264        if let Some(frame) = self.local.transactions.last_mut() {
1265            frame.undo_log.push(StateChange::FkGraphSnapshot {
1266                previous: self.local.graph.foreign_keys.clone(),
1267            });
1268        }
1269    }
1270
1271    fn snapshot_view_graph(&mut self) {
1272        if let Some(frame) = self.local.transactions.last_mut() {
1273            frame.undo_log.push(StateChange::ViewGraphLengthMarker {
1274                len: self.local.graph.views.len(),
1275            });
1276        }
1277    }
1278
1279    fn snapshot_view_graph_full(&mut self) {
1280        if let Some(frame) = self.local.transactions.last_mut() {
1281            frame.undo_log.push(StateChange::ViewGraphSnapshot {
1282                previous: self.local.graph.views.clone(),
1283            });
1284        }
1285    }
1286
1287    fn snapshot_index_graph(&mut self) {
1288        if let Some(frame) = self.local.transactions.last_mut() {
1289            frame.undo_log.push(StateChange::IndexGraphLengthMarker {
1290                len: self.local.graph.indexes.len(),
1291            });
1292        }
1293    }
1294
1295    fn snapshot_index_graph_full(&mut self) {
1296        if let Some(frame) = self.local.transactions.last_mut() {
1297            frame.undo_log.push(StateChange::IndexGraphSnapshot {
1298                previous: self.local.graph.indexes.clone(),
1299            });
1300        }
1301    }
1302
1303    fn snapshot_rename_graph(&mut self) {
1304        if let Some(frame) = self.local.transactions.last_mut() {
1305            frame.undo_log.push(StateChange::RenameGraphLengthMarker {
1306                len: self.local.graph.renames.len(),
1307            });
1308        }
1309    }
1310
1311    fn snapshot_rename_graph_full(&mut self) {
1312        if let Some(frame) = self.local.transactions.last_mut() {
1313            frame.undo_log.push(StateChange::RenameGraphSnapshot {
1314                previous: self.local.graph.renames.clone(),
1315            });
1316        }
1317    }
1318
1319    fn snapshot_sequence_graph(&mut self) {
1320        if let Some(frame) = self.local.transactions.last_mut() {
1321            frame.undo_log.push(StateChange::SequenceGraphLengthMarker {
1322                len: self.local.graph.sequences.len(),
1323            });
1324        }
1325    }
1326
1327    fn snapshot_sequence_graph_full(&mut self) {
1328        if let Some(frame) = self.local.transactions.last_mut() {
1329            frame.undo_log.push(StateChange::SequenceGraphSnapshot {
1330                previous: self.local.graph.sequences.clone(),
1331            });
1332        }
1333    }
1334
1335    fn snapshot_partition_graph(&mut self) {
1336        if let Some(frame) = self.local.transactions.last_mut() {
1337            frame
1338                .undo_log
1339                .push(StateChange::PartitionGraphLengthMarker {
1340                    len: self.local.graph.partitions.len(),
1341                });
1342        }
1343    }
1344
1345    fn snapshot_partition_graph_full(&mut self) {
1346        if let Some(frame) = self.local.transactions.last_mut() {
1347            frame.undo_log.push(StateChange::PartitionGraphSnapshot {
1348                previous: self.local.graph.partitions.clone(),
1349            });
1350        }
1351    }
1352
1353    fn snapshot_search_path(&mut self) {
1354        if let Some(frame) = self.local.transactions.last_mut() {
1355            frame.undo_log.push(StateChange::SearchPathSnapshot {
1356                previous: self.local.search_path.clone(),
1357            });
1358        }
1359    }
1360
1361    fn snapshot_generation_counter(&mut self) {
1362        if let Some(frame) = self.local.transactions.last_mut() {
1363            frame.undo_log.push(StateChange::GenerationCounterSnapshot {
1364                previous: self.local.generation_counter,
1365            });
1366        }
1367    }
1368
1369    fn snapshot_pending_validation(&mut self) {
1370        if let Some(frame) = self.local.transactions.last_mut() {
1371            frame.undo_log.push(StateChange::PendingValidationSnapshot {
1372                previous: self.local.pending_validation.clone(),
1373            });
1374        }
1375    }
1376
1377    fn replay_undo_log(&mut self, frame: TransactionFrame) {
1378        for change in frame.undo_log.into_iter().rev() {
1379            match change {
1380                StateChange::RelationSnapshot { id, previous } => match previous {
1381                    Some(overlay) => {
1382                        self.local.relations.insert(id, overlay);
1383                    }
1384                    None => {
1385                        self.local.relations.remove(&id);
1386                    }
1387                },
1388                StateChange::TypeSnapshot { id, previous } => match previous {
1389                    Some(overlay) => {
1390                        self.local.types.insert(id, overlay);
1391                    }
1392                    None => {
1393                        self.local.types.remove(&id);
1394                    }
1395                },
1396                StateChange::SequenceSnapshot { id, previous } => match previous {
1397                    Some(overlay) => {
1398                        self.local.sequences.insert(id, overlay);
1399                    }
1400                    None => {
1401                        self.local.sequences.remove(&id);
1402                    }
1403                },
1404                StateChange::SearchPathSnapshot { previous } => {
1405                    self.local.search_path = previous;
1406                }
1407                StateChange::GenerationCounterSnapshot { previous } => {
1408                    self.local.generation_counter = previous;
1409                }
1410                StateChange::PendingValidationSnapshot { previous } => {
1411                    self.local.pending_validation = previous;
1412                }
1413                StateChange::FkGraphLengthMarker { len } => {
1414                    self.local.graph.foreign_keys.truncate(len);
1415                }
1416                StateChange::FkGraphSnapshot { previous } => {
1417                    self.local.graph.foreign_keys = previous;
1418                }
1419                StateChange::ViewGraphLengthMarker { len } => {
1420                    self.local.graph.views.truncate(len);
1421                }
1422                StateChange::ViewGraphSnapshot { previous } => {
1423                    self.local.graph.views = previous;
1424                }
1425                StateChange::IndexGraphLengthMarker { len } => {
1426                    self.local.graph.indexes.truncate(len);
1427                }
1428                StateChange::IndexGraphSnapshot { previous } => {
1429                    self.local.graph.indexes = previous;
1430                }
1431                StateChange::RenameGraphLengthMarker { len } => {
1432                    self.local.graph.renames.truncate(len);
1433                }
1434                StateChange::RenameGraphSnapshot { previous } => {
1435                    self.local.graph.renames = previous;
1436                }
1437                StateChange::SequenceGraphLengthMarker { len } => {
1438                    self.local.graph.sequences.truncate(len);
1439                }
1440                StateChange::SequenceGraphSnapshot { previous } => {
1441                    self.local.graph.sequences = previous;
1442                }
1443                StateChange::PartitionGraphLengthMarker { len } => {
1444                    self.local.graph.partitions.truncate(len);
1445                }
1446                StateChange::PartitionGraphSnapshot { previous } => {
1447                    self.local.graph.partitions = previous;
1448                }
1449            }
1450        }
1451    }
1452}