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, PublicationEdge, RenameEdge, SequenceEdge,
5    ViewEdge,
6};
7use crate::analysis::mutations::{
8    AlterTableActionMutation, AlterTypeActionMutation, Mutation, PersistenceMutation,
9};
10use crate::analysis::transaction::{StateChange, TransactionFrame};
11use crate::ast::identifiers::ObjectId;
12use crate::db::cache::DbCache;
13pub use crate::model::relation::RelationOverlay;
14use crate::model::relation::{ColumnAction, Persistence, Privilege, RelationKind, RelationState};
15use crate::model::sequence::{SequenceOverlay, SequenceState};
16use crate::model::trigger::TriggerOverlay;
17use crate::model::types::{TypeKind, TypeOverlay, TypeState};
18use std::collections::{HashMap, HashSet};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum Confidence {
22    Exact,
23    Tainted,
24}
25
26#[derive(Debug, PartialEq, Eq)]
27pub enum MutationResult {
28    Applied,
29    Skipped,
30    Conflict { reason: String },
31}
32
33#[derive(Debug, Default, Clone)]
34pub struct CascadeResult {
35    pub dropped_relations: HashSet<ObjectId>,
36    pub dropped_indexes: HashSet<ObjectId>,
37    pub dropped_constraints: HashSet<(ObjectId, String)>,
38}
39
40pub struct LocalState {
41    pub relations: HashMap<ObjectId, RelationOverlay>,
42    pub types: HashMap<ObjectId, TypeOverlay>,
43    pub functions: HashMap<ObjectId, crate::model::function::FunctionOverlay>,
44    pub sequences: HashMap<ObjectId, SequenceOverlay>,
45    pub publications: HashMap<String, crate::model::replication::PublicationOverlay>,
46    pub subscriptions: HashMap<String, crate::model::replication::SubscriptionOverlay>,
47    pub roles: HashMap<ObjectId, crate::model::role::RoleOverlay>,
48    pub triggers: HashMap<ObjectId, TriggerOverlay>,
49    pub graph: DependencyGraph,
50    pub search_path: Vec<String>,
51    pub current_role: String,
52    pub confidence: Confidence,
53    pub transactions: Vec<TransactionFrame>,
54    pub pending_validation: HashSet<(ObjectId, String)>,
55    pub generation_counter: u64,
56}
57
58#[derive(Clone, Debug)]
59pub struct PreState {
60    pub relations: HashMap<ObjectId, crate::model::relation::RelationState>,
61    pub functions: HashMap<ObjectId, crate::model::function::FunctionState>,
62    pub roles: HashMap<ObjectId, crate::model::role::RoleState>,
63    pub publications: HashMap<String, crate::model::replication::PublicationState>,
64    pub subscriptions: HashMap<String, crate::model::replication::SubscriptionState>,
65    pub sequences: HashMap<ObjectId, crate::model::sequence::SequenceState>,
66    pub types: HashMap<ObjectId, crate::model::types::TypeState>,
67    pub indexes: Vec<crate::analysis::graph::IndexEdge>,
68}
69
70pub struct AnalysisState {
71    pub pg_version_num: Option<u32>,
72    pub baseline_relations: HashSet<ObjectId>,
73    pub baseline_indexes: HashSet<ObjectId>,
74    pub baseline_foreign_keys: HashSet<(ObjectId, String)>,
75    pub baseline_fk_dependencies: HashSet<ObjectId>,
76    pub local: LocalState,
77}
78
79impl AnalysisState {
80    pub fn new(cache: DbCache) -> Self {
81        let mut relations: HashMap<ObjectId, RelationOverlay> = HashMap::new();
82        let mut baseline_relations = HashSet::new();
83        let mut baseline_indexes = HashSet::new();
84        let mut baseline_foreign_keys = HashSet::new();
85        let mut baseline_fk_dependencies = HashSet::new();
86        let mut graph = DependencyGraph::new();
87
88        for (id, rel_state) in cache.baseline_relations() {
89            if rel_state.is_fk_dependency {
90                baseline_fk_dependencies.insert(id.clone());
91            }
92            relations.insert(id.clone(), RelationOverlay::Present(rel_state.clone()));
93            baseline_relations.insert(id.clone());
94        }
95
96        for fk in cache.foreign_keys {
97            baseline_foreign_keys.insert((fk.from_table.clone(), fk.constraint_name.clone()));
98            graph.foreign_keys.push(FkEdge {
99                constraint_name: Some(fk.constraint_name),
100                from_table: fk.from_table,
101                from_columns: Vec::new(),
102                to_table: fk.to_table,
103                to_columns: Vec::new(),
104                from_generation: 0,
105            });
106        }
107
108        for idx in cache.indexes {
109            // BUG-008: index ObjectIds go into baseline_indexes, not baseline_relations
110            baseline_indexes.insert(idx.index_id.clone());
111            graph.indexes.push(IndexEdge {
112                index_id: idx.index_id,
113                relation_id: idx.table_id,
114                using_method: None,
115                has_predicate: false,
116                is_concurrent: false,
117                is_unique: false,
118            });
119        }
120
121        for t in cache.triggers {
122            graph
123                .trigger_dependencies
124                .push(crate::analysis::graph::TriggerEdge {
125                    trigger_id: t.trigger_id,
126                    table_id: t.table_id,
127                    function_id: t.function_id,
128                });
129        }
130
131        let mut functions: HashMap<ObjectId, crate::model::function::FunctionOverlay> =
132            HashMap::new();
133        for (id, func_state) in &cache.functions {
134            functions.insert(
135                id.clone(),
136                crate::model::function::FunctionOverlay::Present(func_state.clone()),
137            );
138        }
139
140        Self {
141            pg_version_num: cache.pg_version_num,
142            baseline_relations,
143            baseline_indexes,
144            baseline_foreign_keys,
145            baseline_fk_dependencies,
146            local: LocalState {
147                relations,
148                types: HashMap::new(),
149                functions,
150                sequences: HashMap::new(),
151                publications: HashMap::new(),
152                subscriptions: HashMap::new(),
153                roles: HashMap::new(),
154                triggers: HashMap::new(),
155                graph,
156                search_path: vec!["public".to_string()],
157                current_role: "postgres".to_string(),
158                confidence: Confidence::Exact,
159                transactions: Vec::new(),
160                pending_validation: HashSet::new(),
161                generation_counter: 0,
162            },
163        }
164    }
165
166    pub fn get_relation(&self, id: &ObjectId) -> Option<&RelationOverlay> {
167        self.local.relations.get(id)
168    }
169
170    pub fn resolve_function_schema(
171        &self,
172        name: &crate::ast::identifiers::QualifiedName,
173        sig_str: &str,
174    ) -> String {
175        if let Some(schema) = &name.schema {
176            return schema.resolve();
177        }
178        for schema in &self.local.search_path {
179            let candidate = ObjectId::new(schema.clone(), sig_str.to_string());
180            if self.local.functions.contains_key(&candidate) {
181                return schema.clone();
182            }
183        }
184        self.local
185            .search_path
186            .first()
187            .cloned()
188            .unwrap_or_else(|| "public".to_string())
189    }
190
191    pub fn resolve_relation_id(&self, name: &crate::ast::identifiers::QualifiedName) -> ObjectId {
192        if let Some(schema) = &name.schema {
193            return ObjectId::new(schema.resolve(), name.name.resolve());
194        }
195        let resolved_name = name.name.resolve();
196        for schema in &self.local.search_path {
197            let mut candidate = ObjectId::new(schema.clone(), resolved_name.clone());
198            if self.local.relations.contains_key(&candidate) {
199                candidate.inferred_schema = true;
200                return candidate;
201            }
202        }
203        let schema = self
204            .local
205            .search_path
206            .first()
207            .cloned()
208            .unwrap_or_else(|| "public".to_string());
209        let mut id = ObjectId::new(schema, resolved_name);
210        id.inferred_schema = true;
211        id
212    }
213
214    pub fn relation_is_present(&self, id: &ObjectId) -> bool {
215        matches!(
216            self.local.relations.get(id),
217            Some(RelationOverlay::Present(_))
218        )
219    }
220
221    pub fn column_was_added_in_transaction(&self, table_id: &ObjectId, column: &str) -> bool {
222        if self.local.transactions.is_empty() {
223            return false;
224        }
225
226        // Search from the oldest transaction frame to the newest
227        for frame in &self.local.transactions {
228            for change in &frame.undo_log {
229                if let StateChange::RelationSnapshot { id, previous } = change
230                    && id == table_id
231                {
232                    match previous.as_ref() {
233                        None | Some(RelationOverlay::Dropped) => {
234                            return true;
235                        }
236                        Some(RelationOverlay::Present(r)) => {
237                            let col_existed = r.columns.iter().any(|c| c.name == column);
238                            return !col_existed;
239                        }
240                    }
241                }
242            }
243        }
244        false
245    }
246
247    pub fn capture_pre_state(&self) -> PreState {
248        let mut relations = HashMap::new();
249        for (id, overlay) in &self.local.relations {
250            if let RelationOverlay::Present(s) = overlay {
251                relations.insert(id.clone(), s.clone());
252            }
253        }
254
255        let mut functions = HashMap::new();
256        for (id, overlay) in &self.local.functions {
257            if let crate::model::function::FunctionOverlay::Present(s) = overlay {
258                functions.insert(id.clone(), s.clone());
259            }
260        }
261
262        let mut roles = HashMap::new();
263        for (name, overlay) in &self.local.roles {
264            if let crate::model::role::RoleOverlay::Present(s) = overlay {
265                roles.insert(name.clone(), s.clone());
266            }
267        }
268
269        let mut publications = HashMap::new();
270        for (name, overlay) in &self.local.publications {
271            if let crate::model::replication::PublicationOverlay::Present(s) = overlay {
272                publications.insert(name.clone(), s.clone());
273            }
274        }
275
276        let mut subscriptions = HashMap::new();
277        for (name, overlay) in &self.local.subscriptions {
278            if let crate::model::replication::SubscriptionOverlay::Present(s) = overlay {
279                subscriptions.insert(name.clone(), s.clone());
280            }
281        }
282
283        let mut sequences = HashMap::new();
284        for (id, overlay) in &self.local.sequences {
285            if let SequenceOverlay::Present(s) = overlay {
286                sequences.insert(id.clone(), s.clone());
287            }
288        }
289
290        let mut types = HashMap::new();
291        for (id, overlay) in &self.local.types {
292            if let TypeOverlay::Present(s) = overlay {
293                types.insert(id.clone(), s.clone());
294            }
295        }
296
297        let indexes = self.local.graph.indexes.clone();
298
299        PreState {
300            relations,
301            functions,
302            roles,
303            publications,
304            subscriptions,
305            sequences,
306            types,
307            indexes,
308        }
309    }
310
311    pub fn get_cascade_closure(&self, target_oid: &ObjectId) -> CascadeResult {
312        let mut result = CascadeResult::default();
313        let mut visited = HashSet::new();
314        self.walk_cascade(target_oid, &mut visited, &mut result);
315        result
316    }
317
318    fn walk_cascade(
319        &self,
320        current: &ObjectId,
321        visited: &mut HashSet<ObjectId>,
322        result: &mut CascadeResult,
323    ) {
324        let resolved_current = self.local.graph.resolve_rename(current).clone();
325
326        if !visited.insert(resolved_current.clone()) {
327            return;
328        }
329
330        result.dropped_relations.insert(resolved_current.clone());
331
332        for view_edge in &self.local.graph.views {
333            if view_edge
334                .depends_on
335                .iter()
336                .any(|dep| self.local.graph.resolve_rename(dep) == &resolved_current)
337            {
338                let resolved_view_id = self.local.graph.resolve_rename(&view_edge.view_id).clone();
339                if !visited.contains(&resolved_view_id) {
340                    self.walk_cascade(&resolved_view_id, visited, result);
341                }
342            }
343        }
344
345        for index_edge in &self.local.graph.indexes {
346            if self.local.graph.resolve_rename(&index_edge.relation_id) == &resolved_current {
347                result.dropped_indexes.insert(
348                    self.local
349                        .graph
350                        .resolve_rename(&index_edge.index_id)
351                        .clone(),
352                );
353            }
354        }
355
356        for fk_edge in &self.local.graph.foreign_keys {
357            if self.local.graph.resolve_rename(&fk_edge.to_table) == &resolved_current
358                && let Some(cname) = &fk_edge.constraint_name
359            {
360                result.dropped_constraints.insert((
361                    self.local.graph.resolve_rename(&fk_edge.from_table).clone(),
362                    cname.clone(),
363                ));
364            }
365        }
366
367        for partition_edge in &self.local.graph.partitions {
368            if self.local.graph.resolve_rename(&partition_edge.parent) == &resolved_current {
369                let resolved_child = self
370                    .local
371                    .graph
372                    .resolve_rename(&partition_edge.child)
373                    .clone();
374                if !visited.contains(&resolved_child) {
375                    self.walk_cascade(&resolved_child, visited, result);
376                }
377            }
378        }
379    }
380
381    fn resolve_grant_privileges(
382        spec: &crate::analysis::facts::PrivilegeSpec,
383    ) -> HashSet<Privilege> {
384        match spec {
385            crate::analysis::facts::PrivilegeSpec::All => vec![
386                Privilege::Select,
387                Privilege::Insert,
388                Privilege::Update,
389                Privilege::Delete,
390                Privilege::Truncate,
391                Privilege::References,
392                Privilege::Trigger,
393            ]
394            .into_iter()
395            .collect(),
396            crate::analysis::facts::PrivilegeSpec::List(list) => list
397                .iter()
398                .filter_map(|p| match p {
399                    crate::analysis::facts::PrivilegeFact::Select => Some(Privilege::Select),
400                    crate::analysis::facts::PrivilegeFact::Insert => Some(Privilege::Insert),
401                    crate::analysis::facts::PrivilegeFact::Update => Some(Privilege::Update),
402                    crate::analysis::facts::PrivilegeFact::Delete => Some(Privilege::Delete),
403                    crate::analysis::facts::PrivilegeFact::Truncate => Some(Privilege::Truncate),
404                    crate::analysis::facts::PrivilegeFact::References => {
405                        Some(Privilege::References)
406                    }
407                    crate::analysis::facts::PrivilegeFact::Trigger => Some(Privilege::Trigger),
408                    _ => None,
409                })
410                .collect(),
411        }
412    }
413
414    fn resolve_role_name(
415        role: &crate::analysis::facts::RoleFact,
416        current_role: &str,
417    ) -> Option<ObjectId> {
418        let name = match role {
419            crate::analysis::facts::RoleFact::Named { name, .. } => Some(name.clone()),
420            crate::analysis::facts::RoleFact::CurrentUser
421            | crate::analysis::facts::RoleFact::CurrentRole => Some(current_role.to_string()),
422            crate::analysis::facts::RoleFact::SessionUser => Some("postgres".to_string()),
423            crate::analysis::facts::RoleFact::Unknown => None,
424        }?;
425        Some(ObjectId::new("", name))
426    }
427
428    fn apply_grant_to_relation(
429        &mut self,
430        id: &ObjectId,
431        privileges: &HashSet<Privilege>,
432        grantees: &[crate::analysis::facts::RoleFact],
433    ) {
434        self.snapshot_relation(id);
435        if let Some(RelationOverlay::Present(rel)) = self.local.relations.get_mut(id) {
436            for grantee in grantees {
437                if let Some(role_id) = Self::resolve_role_name(grantee, &self.local.current_role) {
438                    rel.privileges.grant(role_id, privileges.clone());
439                }
440            }
441        }
442    }
443
444    fn apply_revoke_to_relation(
445        &mut self,
446        id: &ObjectId,
447        privileges: &HashSet<Privilege>,
448        revokees: &[crate::analysis::facts::RoleFact],
449    ) {
450        self.snapshot_relation(id);
451        if let Some(RelationOverlay::Present(rel)) = self.local.relations.get_mut(id) {
452            for revokee in revokees {
453                if let Some(role_id) = Self::resolve_role_name(revokee, &self.local.current_role) {
454                    rel.privileges.revoke(&role_id, privileges);
455                }
456            }
457        }
458    }
459
460    pub fn apply(
461        &mut self,
462        mutation: &Mutation,
463        precomputed_cascade: Option<&CascadeResult>,
464    ) -> MutationResult {
465        match mutation {
466            Mutation::CreateSchema(_) => MutationResult::Applied,
467            Mutation::DropSchema(drop_schema) => {
468                if drop_schema.cascade {
469                    let mut relations_to_drop = Vec::new();
470                    for id in self.local.relations.keys() {
471                        if drop_schema.names.contains(&id.schema) {
472                            relations_to_drop.push(id.clone());
473                        }
474                    }
475                    for id in relations_to_drop {
476                        self.snapshot_relation(&id);
477                        self.local.relations.insert(id, RelationOverlay::Dropped);
478                    }
479
480                    let mut types_to_drop = Vec::new();
481                    for id in self.local.types.keys() {
482                        if drop_schema.names.contains(&id.schema) {
483                            types_to_drop.push(id.clone());
484                        }
485                    }
486                    for id in types_to_drop {
487                        self.snapshot_type(&id);
488                        self.local.types.insert(id, TypeOverlay::Dropped);
489                    }
490
491                    let mut seqs_to_drop = Vec::new();
492                    for id in self.local.sequences.keys() {
493                        if drop_schema.names.contains(&id.schema) {
494                            seqs_to_drop.push(id.clone());
495                        }
496                    }
497                    for id in seqs_to_drop {
498                        self.snapshot_sequence(&id);
499                        self.local.sequences.insert(id, SequenceOverlay::Dropped);
500                    }
501
502                    self.snapshot_fk_graph_full();
503                    self.snapshot_view_graph_full();
504                    self.snapshot_index_graph_full();
505                    self.snapshot_partition_graph_full();
506                    self.snapshot_sequence_graph_full();
507                    self.snapshot_rename_graph_full();
508                    self.snapshot_trigger_graph_full();
509                    self.snapshot_publication_graph_full();
510
511                    let g = &mut self.local.graph;
512                    g.foreign_keys.retain(|fk| {
513                        !drop_schema.names.contains(&fk.from_table.schema)
514                            && !drop_schema.names.contains(&fk.to_table.schema)
515                    });
516                    g.views
517                        .retain(|v| !drop_schema.names.contains(&v.view_id.schema));
518                    g.indexes
519                        .retain(|idx| !drop_schema.names.contains(&idx.index_id.schema));
520                    g.partitions.retain(|p| {
521                        !drop_schema.names.contains(&p.parent.schema)
522                            && !drop_schema.names.contains(&p.child.schema)
523                    });
524                    g.sequences
525                        .retain(|s| !drop_schema.names.contains(&s.sequence_id.schema));
526                    g.renames.retain(|r| {
527                        !drop_schema.names.contains(&r.from.schema)
528                            && !drop_schema.names.contains(&r.to.schema)
529                    });
530                    g.trigger_dependencies.retain(|t| {
531                        !drop_schema.names.contains(&t.trigger_id.schema)
532                            && !drop_schema.names.contains(&t.table_id.schema)
533                            && !drop_schema.names.contains(&t.function_id.schema)
534                    });
535                    g.publication_dependencies
536                        .retain(|p| !drop_schema.names.contains(&p.table_id.schema));
537                }
538                MutationResult::Applied
539            }
540            Mutation::DropTable(drop_table) => {
541                if !self.relation_is_present(&drop_table.id) {
542                    if drop_table.if_exists {
543                        return MutationResult::Skipped;
544                    } else {
545                        self.local.confidence = Confidence::Tainted;
546                        return MutationResult::Skipped;
547                    }
548                }
549
550                // Cleanup trigger dependencies
551                self.snapshot_trigger_graph_full();
552                self.local
553                    .graph
554                    .trigger_dependencies
555                    .retain(|t| t.table_id != drop_table.id);
556                // Also remove the trigger states themselves if cascading (or simply leave them orphaned)
557                // For now, assume trigger state drops with the table in a cascade
558                if drop_table.cascade {
559                    let triggers_to_drop: Vec<ObjectId> = self
560                        .local
561                        .triggers
562                        .iter()
563                        .filter_map(|(id, overlay)| {
564                            if let TriggerOverlay::Present(t) = overlay {
565                                if t.table_id == drop_table.id {
566                                    Some(id.clone())
567                                } else {
568                                    None
569                                }
570                            } else {
571                                None
572                            }
573                        })
574                        .collect();
575                    for tid in triggers_to_drop {
576                        self.snapshot_trigger(&tid);
577                        self.local.triggers.insert(tid, TriggerOverlay::Dropped);
578                    }
579                }
580
581                let renames = self.local.graph.renames.clone();
582                let resolve = |id: &ObjectId| -> ObjectId {
583                    let mut current = id;
584                    loop {
585                        match renames.iter().find(|r| &r.from == current) {
586                            Some(edge) => current = &edge.to,
587                            None => return current.clone(),
588                        }
589                    }
590                };
591
592                let resolved_drop = resolve(&drop_table.id);
593
594                if drop_table.cascade {
595                    let local_closure;
596                    let closure = match precomputed_cascade {
597                        Some(c) => c,
598                        None => {
599                            local_closure = self.get_cascade_closure(&drop_table.id);
600                            &local_closure
601                        }
602                    };
603
604                    for dropped_rel_id in &closure.dropped_relations {
605                        self.snapshot_relation(dropped_rel_id);
606                        self.local
607                            .relations
608                            .insert(dropped_rel_id.clone(), RelationOverlay::Dropped);
609                    }
610
611                    self.snapshot_index_graph_full();
612                    self.local
613                        .graph
614                        .indexes
615                        .retain(|idx| !closure.dropped_indexes.contains(&resolve(&idx.index_id)));
616
617                    self.snapshot_fk_graph_full();
618                    self.local.graph.foreign_keys.retain(|fk| {
619                        let from_dropped =
620                            closure.dropped_relations.contains(&resolve(&fk.from_table));
621                        let to_dropped = closure.dropped_relations.contains(&resolve(&fk.to_table));
622                        let constraint_explicitly_dropped = if let Some(cname) = &fk.constraint_name
623                        {
624                            closure
625                                .dropped_constraints
626                                .contains(&(resolve(&fk.from_table), cname.clone()))
627                        } else {
628                            false
629                        };
630                        !(from_dropped || to_dropped || constraint_explicitly_dropped)
631                    });
632
633                    self.snapshot_view_graph_full();
634                    self.local
635                        .graph
636                        .views
637                        .retain(|v| !closure.dropped_relations.contains(&resolve(&v.view_id)));
638
639                    self.snapshot_sequence_graph_full();
640                    self.local.graph.sequences.retain(|s| {
641                        let resolved_table = resolve(&s.table_id);
642                        !closure.dropped_relations.contains(&resolved_table)
643                    });
644                } else {
645                    let has_view_deps = self
646                        .local
647                        .graph
648                        .views
649                        .iter()
650                        .any(|v| v.depends_on.iter().any(|dep| resolve(dep) == resolved_drop));
651                    let has_fk_deps = self.local.graph.foreign_keys.iter().any(|fk| {
652                        resolve(&fk.to_table) == resolved_drop
653                            && resolve(&fk.from_table) != resolved_drop
654                    });
655                    let has_partition_deps = self
656                        .local
657                        .graph
658                        .partitions
659                        .iter()
660                        .any(|p| resolve(&p.parent) == resolved_drop);
661
662                    if has_view_deps || has_fk_deps || has_partition_deps {
663                        self.local.confidence = Confidence::Tainted;
664                        return MutationResult::Skipped;
665                    }
666
667                    self.snapshot_relation(&drop_table.id);
668                    self.local
669                        .relations
670                        .insert(drop_table.id.clone(), RelationOverlay::Dropped);
671
672                    self.snapshot_sequence_graph_full();
673                    self.local
674                        .graph
675                        .sequences
676                        .retain(|s| resolve(&s.table_id) != resolved_drop);
677                }
678
679                self.snapshot_partition_graph_full();
680                self.local.graph.partitions.retain(|p| {
681                    resolve(&p.parent) != resolved_drop && resolve(&p.child) != resolved_drop
682                });
683
684                MutationResult::Applied
685            }
686            Mutation::CreateTable(create) => {
687                if create.if_not_exists && self.relation_is_present(&create.id) {
688                    return MutationResult::Skipped;
689                }
690                if !create.if_not_exists && self.relation_is_present(&create.id) {
691                    return MutationResult::Conflict {
692                        reason: format!("relation '{}' already exists", create.id),
693                    };
694                }
695
696                self.snapshot_relation(&create.id);
697
698                self.snapshot_generation_counter();
699                self.local.generation_counter += 1;
700                let generation = self.local.generation_counter;
701
702                let resolved_persistence = match create.persistence {
703                    PersistenceMutation::Permanent => {
704                        crate::model::relation::Persistence::Permanent
705                    }
706                    PersistenceMutation::Temporary => {
707                        crate::model::relation::Persistence::Temporary
708                    }
709                    PersistenceMutation::Unlogged => crate::model::relation::Persistence::Unlogged,
710                };
711
712                let mut rel_state = RelationState::new(
713                    create.id.clone(),
714                    ObjectId::new("public", &self.local.current_role),
715                    generation,
716                    if create.as_select { None } else { Some(0) },
717                    RelationKind::Table,
718                    resolved_persistence,
719                    self.local.transactions.len(),
720                );
721
722                // Store partition strategy information
723                rel_state.partition_type = create
724                    .partition_by
725                    .as_ref()
726                    .and_then(|pb| pb.split_whitespace().nth(2).map(|s| s.to_uppercase()))
727                    .or_else(|| {
728                        create.partition_of.as_ref().and_then(|parent_id| {
729                            self.local.relations.get(parent_id).and_then(|r| {
730                                if let RelationOverlay::Present(rel) = r {
731                                    rel.partition_type.clone()
732                                } else {
733                                    None
734                                }
735                            })
736                        })
737                    });
738                rel_state.partition_by = create.partition_by.clone();
739
740                let pk_columns: HashSet<&str> = create
741                    .table_constraints
742                    .iter()
743                    .filter_map(|tc| {
744                        if let TableConstraintFact::PrimaryKey { columns } = tc {
745                            Some(columns.iter().map(|s| s.as_str()))
746                        } else {
747                            None
748                        }
749                    })
750                    .flatten()
751                    .collect();
752
753                for col in &create.columns {
754                    let is_pk = col.is_primary_key || pk_columns.contains(col.name.as_str());
755                    rel_state.apply_column_action(&ColumnAction::Add {
756                        name: col.name.clone(),
757                        data_type: col.ty.clone(),
758                        not_null: col.not_null || is_pk,
759                        default: col.default.clone(),
760                    });
761                }
762
763                self.local
764                    .relations
765                    .insert(create.id.clone(), RelationOverlay::Present(rel_state));
766
767                if let Some(parent_id) = &create.partition_of {
768                    self.snapshot_partition_graph();
769                    self.local.graph.partitions.push(PartitionEdge {
770                        parent: parent_id.clone(),
771                        child: create.id.clone(),
772                    });
773                }
774
775                if !create.foreign_keys.is_empty() {
776                    self.snapshot_fk_graph();
777                }
778
779                for fk in &create.foreign_keys {
780                    self.local.graph.foreign_keys.push(FkEdge {
781                        constraint_name: fk.constraint_name.clone(),
782                        from_table: create.id.clone(),
783                        from_columns: fk.from_columns.clone(),
784                        to_table: fk.to_table.clone(),
785                        to_columns: fk.to_columns.clone(),
786                        from_generation: generation,
787                    });
788                }
789                MutationResult::Applied
790            }
791            Mutation::CreateView(create_view) => {
792                if !create_view.or_replace && self.relation_is_present(&create_view.id) {
793                    return MutationResult::Conflict {
794                        reason: format!("relation '{}' already exists", create_view.id),
795                    };
796                }
797                self.snapshot_relation(&create_view.id);
798                self.snapshot_generation_counter();
799                self.local.generation_counter += 1;
800                let generation = self.local.generation_counter;
801
802                self.local.relations.insert(
803                    create_view.id.clone(),
804                    RelationOverlay::Present(RelationState::new(
805                        create_view.id.clone(),
806                        ObjectId::new("public", &self.local.current_role),
807                        generation,
808                        None,
809                        RelationKind::View,
810                        Persistence::Permanent,
811                        self.local.transactions.len(),
812                    )),
813                );
814
815                self.snapshot_view_graph();
816                self.local.graph.views.push(ViewEdge {
817                    view_id: create_view.id.clone(),
818                    depends_on: create_view.depends_on.clone(),
819                    view_generation: generation,
820                });
821                MutationResult::Applied
822            }
823            Mutation::CreateMaterializedView(create_mv) => {
824                if self.relation_is_present(&create_mv.id) {
825                    return MutationResult::Conflict {
826                        reason: format!("relation '{}' already exists", create_mv.id),
827                    };
828                }
829                self.snapshot_relation(&create_mv.id);
830                self.snapshot_generation_counter();
831                self.local.generation_counter += 1;
832                let generation = self.local.generation_counter;
833
834                self.local.relations.insert(
835                    create_mv.id.clone(),
836                    RelationOverlay::Present(RelationState::new(
837                        create_mv.id.clone(),
838                        ObjectId::new("public", &self.local.current_role),
839                        generation,
840                        None,
841                        RelationKind::MaterializedView,
842                        Persistence::Permanent,
843                        self.local.transactions.len(),
844                    )),
845                );
846
847                self.snapshot_view_graph();
848                self.local.graph.views.push(ViewEdge {
849                    view_id: create_mv.id.clone(),
850                    depends_on: create_mv.depends_on.clone(),
851                    view_generation: generation,
852                });
853                MutationResult::Applied
854            }
855            Mutation::RefreshMaterializedView(_) => MutationResult::Applied,
856            Mutation::CreateIndex(create_idx) => {
857                let exists = self
858                    .local
859                    .graph
860                    .indexes
861                    .iter()
862                    .any(|ix| ix.index_id == create_idx.id);
863                if create_idx.if_not_exists && exists {
864                    return MutationResult::Skipped;
865                }
866                if !create_idx.if_not_exists && exists {
867                    return MutationResult::Conflict {
868                        reason: format!("relation '{}' already exists", create_idx.id),
869                    };
870                }
871                self.snapshot_index_graph();
872                self.local.graph.indexes.push(IndexEdge {
873                    index_id: create_idx.id.clone(),
874                    relation_id: create_idx.table.clone(),
875                    using_method: create_idx.using_method.clone(),
876                    has_predicate: create_idx.has_predicate,
877                    is_concurrent: create_idx.concurrently,
878                    is_unique: create_idx.unique,
879                });
880                MutationResult::Applied
881            }
882            Mutation::CreatePolicy(create_policy) => {
883                self.snapshot_relation(&create_policy.table);
884                if let Some(RelationOverlay::Present(rel)) =
885                    self.local.relations.get_mut(&create_policy.table)
886                {
887                    rel.policies.insert(create_policy.name.clone());
888                }
889                MutationResult::Applied
890            }
891            Mutation::DropPolicy(drop_policy) => {
892                self.snapshot_relation(&drop_policy.table);
893                if let Some(RelationOverlay::Present(rel)) =
894                    self.local.relations.get_mut(&drop_policy.table)
895                {
896                    rel.policies.remove(&drop_policy.name);
897                }
898                MutationResult::Applied
899            }
900            Mutation::CreateTrigger(create_trigger) => {
901                let trigger_id = ObjectId::new(
902                    create_trigger.table.schema.clone(),
903                    create_trigger.name.clone(),
904                );
905                self.snapshot_trigger(&trigger_id);
906                self.local.triggers.insert(
907                    trigger_id.clone(),
908                    TriggerOverlay::Present(crate::model::trigger::TriggerState {
909                        id: trigger_id.clone(),
910                        table_id: create_trigger.table.clone(),
911                        generation: self.local.generation_counter,
912                    }),
913                );
914
915                self.snapshot_relation(&create_trigger.table);
916                if let Some(RelationOverlay::Present(rel)) =
917                    self.local.relations.get_mut(&create_trigger.table)
918                {
919                    rel.triggers.insert(create_trigger.name.clone());
920                }
921
922                self.snapshot_trigger_graph_full();
923                self.local
924                    .graph
925                    .trigger_dependencies
926                    .push(crate::analysis::graph::TriggerEdge {
927                        trigger_id,
928                        table_id: create_trigger.table.clone(),
929                        function_id: create_trigger.function_id.clone(),
930                    });
931
932                MutationResult::Applied
933            }
934            Mutation::DropTrigger(drop_trigger) => {
935                let trigger_id =
936                    ObjectId::new(drop_trigger.table.schema.clone(), drop_trigger.name.clone());
937                self.snapshot_trigger(&trigger_id);
938                self.local
939                    .triggers
940                    .insert(trigger_id.clone(), TriggerOverlay::Dropped);
941
942                self.snapshot_relation(&drop_trigger.table);
943                if let Some(RelationOverlay::Present(rel)) =
944                    self.local.relations.get_mut(&drop_trigger.table)
945                {
946                    rel.triggers.remove(&drop_trigger.name);
947                }
948
949                self.snapshot_trigger_graph_full();
950                self.local
951                    .graph
952                    .trigger_dependencies
953                    .retain(|t| t.trigger_id != trigger_id);
954
955                MutationResult::Applied
956            }
957            Mutation::AlterTable(alter) => {
958                self.snapshot_relation(&alter.id);
959                let rel_overlay = self.local.relations.get_mut(&alter.id);
960                if let Some(RelationOverlay::Present(rel)) = rel_overlay {
961                    let generation = rel.generation;
962                    match &alter.action {
963                        AlterTableActionMutation::AddColumn {
964                            name,
965                            ty,
966                            if_not_exists,
967                            not_null,
968                            default,
969                            depends_on,
970                        } => {
971                            if !(*if_not_exists && rel.has_column(name)) {
972                                if let Some(existing_col) =
973                                    rel.columns.iter().find(|c| c.name == *name)
974                                    && existing_col.data_type.as_deref() != ty.as_deref()
975                                {
976                                    return MutationResult::Conflict {
977                                        reason: format!(
978                                            "column '{}' already added with type {} (likely an earlier file in this chain), this file adds it again with type {}",
979                                            name,
980                                            existing_col.data_type.as_deref().unwrap_or("unknown"),
981                                            ty.as_deref().unwrap_or("unknown")
982                                        ),
983                                    };
984                                }
985                                rel.apply_column_action(&ColumnAction::Add {
986                                    name: name.clone(),
987                                    data_type: ty.clone(),
988                                    not_null: *not_null,
989                                    default: default.clone(),
990                                });
991
992                                if let Some((source_table, source_col)) = depends_on {
993                                    self.snapshot_column_graph();
994                                    self.local.graph.column_dependencies.push(
995                                        crate::analysis::graph::ColumnDependencyEdge {
996                                            table_id: alter.id.clone(),
997                                            column: name.clone(),
998                                            depends_on_table: source_table.clone(),
999                                            depends_on_column: source_col.clone(),
1000                                        },
1001                                    );
1002                                }
1003                            }
1004                        }
1005                        AlterTableActionMutation::DropColumn { name, if_exists } => {
1006                            if !rel.has_column(name) {
1007                                if *if_exists {
1008                                    // Column doesn't exist and IF EXISTS was specified: no-op
1009                                    return MutationResult::Skipped;
1010                                }
1011                                // Column doesn't exist and IF EXISTS not specified: PG runtime error
1012                                self.local.confidence = Confidence::Tainted;
1013                                return MutationResult::Skipped;
1014                            }
1015                            rel.apply_column_action(&ColumnAction::Drop { name: name.clone() });
1016                        }
1017                        AlterTableActionMutation::RenameColumn { from, to } => {
1018                            rel.apply_column_action(&ColumnAction::Rename {
1019                                from: from.clone(),
1020                                to: to.clone(),
1021                            });
1022                        }
1023                        AlterTableActionMutation::SetNotNull { column } => {
1024                            rel.apply_column_action(&ColumnAction::SetNotNull {
1025                                name: column.clone(),
1026                            });
1027                        }
1028                        AlterTableActionMutation::DropNotNull { column } => {
1029                            rel.apply_column_action(&ColumnAction::DropNotNull {
1030                                name: column.clone(),
1031                            });
1032                        }
1033                        AlterTableActionMutation::SetType { column, ty, .. } => {
1034                            if !rel.has_column(column) {
1035                                self.local.confidence = Confidence::Tainted;
1036                            }
1037                            rel.apply_column_action(&ColumnAction::SetType {
1038                                name: column.clone(),
1039                                data_type: ty.clone(),
1040                            });
1041                        }
1042                        AlterTableActionMutation::SetDefault { column, default } => {
1043                            if !rel.has_column(column) {
1044                                self.local.confidence = Confidence::Tainted;
1045                            }
1046                            rel.apply_column_action(&ColumnAction::SetDefault {
1047                                name: column.clone(),
1048                                default: default.clone(),
1049                            });
1050                        }
1051                        AlterTableActionMutation::AddForeignKey {
1052                            constraint_name,
1053                            to_table,
1054                            from_columns,
1055                            to_columns,
1056                            ..
1057                        } => {
1058                            self.snapshot_fk_graph();
1059                            self.local.graph.foreign_keys.push(FkEdge {
1060                                constraint_name: constraint_name.clone(),
1061                                from_table: alter.id.clone(),
1062                                from_columns: from_columns.clone(),
1063                                to_table: to_table.clone(),
1064                                to_columns: to_columns.clone(),
1065                                from_generation: generation,
1066                            });
1067                        }
1068                        AlterTableActionMutation::DropConstraint { name } => {
1069                            self.snapshot_fk_graph();
1070                            self.local.graph.foreign_keys.retain(|fk| {
1071                                !(fk.from_table == alter.id
1072                                    && fk.constraint_name.as_ref() == Some(name))
1073                            });
1074                        }
1075                        AlterTableActionMutation::AttachPartition { child } => {
1076                            // BUG-012: Reject cycle topologies before inserting the edge.
1077                            if self.local.graph.check_partition_cycle(&alter.id, child) {
1078                                self.snapshot_confidence();
1079                                self.local.confidence = Confidence::Tainted;
1080                            } else {
1081                                self.snapshot_partition_graph();
1082                                self.local.graph.partitions.push(PartitionEdge {
1083                                    parent: alter.id.clone(),
1084                                    child: child.clone(),
1085                                });
1086                            }
1087                        }
1088                        AlterTableActionMutation::DetachPartition { child } => {
1089                            self.snapshot_partition_graph();
1090                            self.local
1091                                .graph
1092                                .partitions
1093                                .retain(|p| !(p.parent == alter.id && p.child == *child));
1094                        }
1095                        _ => {}
1096                    }
1097                }
1098                MutationResult::Applied
1099            }
1100            Mutation::CreateType(create_type) => {
1101                if matches!(
1102                    self.local.types.get(&create_type.id),
1103                    Some(TypeOverlay::Present(_))
1104                ) {
1105                    return MutationResult::Conflict {
1106                        reason: format!("type '{}' already exists", create_type.id),
1107                    };
1108                }
1109                self.snapshot_type(&create_type.id);
1110                self.snapshot_generation_counter();
1111                self.local.generation_counter += 1;
1112                let generation = self.local.generation_counter;
1113
1114                self.local.types.insert(
1115                    create_type.id.clone(),
1116                    TypeOverlay::Present(TypeState {
1117                        id: create_type.id.clone(),
1118                        generation,
1119                        kind: create_type.kind.clone(),
1120                    }),
1121                );
1122                MutationResult::Applied
1123            }
1124            Mutation::AlterType(alter_type) => {
1125                self.snapshot_type(&alter_type.id);
1126                if let Some(TypeOverlay::Present(t)) = self.local.types.get_mut(&alter_type.id) {
1127                    match &alter_type.action {
1128                        AlterTypeActionMutation::AddValue { new_value } => {
1129                            if let TypeKind::Enum { variants } = &mut t.kind {
1130                                variants.push(new_value.clone());
1131                            }
1132                        }
1133                    }
1134                }
1135                MutationResult::Applied
1136            }
1137            Mutation::CreateDomain(create_domain) => {
1138                if matches!(
1139                    self.local.types.get(&create_domain.id),
1140                    Some(TypeOverlay::Present(_))
1141                ) {
1142                    return MutationResult::Conflict {
1143                        reason: format!("type '{}' already exists", create_domain.id),
1144                    };
1145                }
1146                self.snapshot_type(&create_domain.id);
1147                self.snapshot_generation_counter();
1148                self.local.generation_counter += 1;
1149                let generation = self.local.generation_counter;
1150
1151                self.local.types.insert(
1152                    create_domain.id.clone(),
1153                    TypeOverlay::Present(TypeState {
1154                        id: create_domain.id.clone(),
1155                        generation,
1156                        kind: TypeKind::Domain {
1157                            base_type: create_domain.base_type.clone(),
1158                        },
1159                    }),
1160                );
1161                MutationResult::Applied
1162            }
1163            Mutation::AlterDomain(_) => MutationResult::Applied,
1164            Mutation::DropDomain(drop_domain) => {
1165                for id in &drop_domain.ids {
1166                    self.snapshot_type(id);
1167                    self.local.types.insert(id.clone(), TypeOverlay::Dropped);
1168                }
1169                MutationResult::Applied
1170            }
1171            Mutation::DropType(drop_type) => {
1172                for id in &drop_type.ids {
1173                    self.snapshot_type(id);
1174                    self.local.types.insert(id.clone(), TypeOverlay::Dropped);
1175                }
1176                MutationResult::Applied
1177            }
1178            Mutation::CreateSequence(create_seq) => {
1179                if create_seq.if_not_exists && self.local.sequences.contains_key(&create_seq.id) {
1180                    return MutationResult::Skipped;
1181                }
1182                if !create_seq.if_not_exists && self.local.sequences.contains_key(&create_seq.id) {
1183                    return MutationResult::Conflict {
1184                        reason: format!("relation '{}' already exists", create_seq.id),
1185                    };
1186                }
1187                self.snapshot_sequence(&create_seq.id);
1188                self.snapshot_generation_counter();
1189                self.local.generation_counter += 1;
1190                let generation = self.local.generation_counter;
1191
1192                self.local.sequences.insert(
1193                    create_seq.id.clone(),
1194                    SequenceOverlay::Present(SequenceState {
1195                        id: create_seq.id.clone(),
1196                        generation,
1197                    }),
1198                );
1199
1200                if let Some((table_id, col)) = &create_seq.owned_by {
1201                    self.snapshot_sequence_graph();
1202                    self.local.graph.sequences.push(SequenceEdge {
1203                        sequence_id: create_seq.id.clone(),
1204                        table_id: table_id.clone(),
1205                        column: col.clone(),
1206                    });
1207                }
1208                MutationResult::Applied
1209            }
1210            Mutation::AlterSequence(alter_seq) => {
1211                self.snapshot_sequence(&alter_seq.id);
1212                self.snapshot_sequence_graph();
1213                self.local
1214                    .graph
1215                    .sequences
1216                    .retain(|s| s.sequence_id != alter_seq.id);
1217                if let Some((table_id, col)) = &alter_seq.owned_by {
1218                    self.local.graph.sequences.push(SequenceEdge {
1219                        sequence_id: alter_seq.id.clone(),
1220                        table_id: table_id.clone(),
1221                        column: col.clone(),
1222                    });
1223                }
1224                MutationResult::Applied
1225            }
1226            Mutation::DropSequence(drop_seq) => {
1227                for id in &drop_seq.ids {
1228                    self.snapshot_sequence(id);
1229                    self.local
1230                        .sequences
1231                        .insert(id.clone(), SequenceOverlay::Dropped);
1232                }
1233                self.snapshot_sequence_graph_full();
1234                self.local
1235                    .graph
1236                    .sequences
1237                    .retain(|s| !drop_seq.ids.contains(&s.sequence_id));
1238                MutationResult::Applied
1239            }
1240            Mutation::Rename(rename) => {
1241                self.snapshot_relation(&rename.old_id);
1242                self.snapshot_relation(&rename.new_id);
1243                if let Some(RelationOverlay::Present(mut state)) =
1244                    self.local.relations.remove(&rename.old_id)
1245                {
1246                    state.id = rename.new_id.clone();
1247                    self.local
1248                        .relations
1249                        .insert(rename.new_id.clone(), RelationOverlay::Present(state));
1250                }
1251                self.snapshot_rename_graph();
1252                self.local.graph.renames.push(RenameEdge {
1253                    from: rename.old_id.clone(),
1254                    to: rename.new_id.clone(),
1255                });
1256
1257                // Snapshot all 8 affected graph edge lists before calling propagate_rename
1258                self.snapshot_fk_graph_full();
1259                self.snapshot_view_graph_full();
1260                self.snapshot_index_graph_full();
1261                self.snapshot_partition_graph_full();
1262                self.snapshot_sequence_graph_full();
1263                self.snapshot_column_graph_full();
1264                self.snapshot_trigger_graph_full();
1265                self.snapshot_publication_graph_full();
1266
1267                self.local
1268                    .graph
1269                    .propagate_rename(&rename.old_id, &rename.new_id);
1270
1271                MutationResult::Applied
1272            }
1273            Mutation::DropView(drop_view) => {
1274                for id in &drop_view.ids {
1275                    self.snapshot_relation(id);
1276                    self.local
1277                        .relations
1278                        .insert(id.clone(), RelationOverlay::Dropped);
1279                }
1280                self.snapshot_view_graph_full();
1281                self.local
1282                    .graph
1283                    .views
1284                    .retain(|v| !drop_view.ids.contains(&v.view_id));
1285                MutationResult::Applied
1286            }
1287            Mutation::DropMaterializedView(drop_mv) => {
1288                for id in &drop_mv.ids {
1289                    self.snapshot_relation(id);
1290                    self.local
1291                        .relations
1292                        .insert(id.clone(), RelationOverlay::Dropped);
1293                }
1294                self.snapshot_view_graph_full();
1295                self.local
1296                    .graph
1297                    .views
1298                    .retain(|v| !drop_mv.ids.contains(&v.view_id));
1299                MutationResult::Applied
1300            }
1301            Mutation::DropIndex(drop_idx) => {
1302                self.snapshot_index_graph();
1303                self.local
1304                    .graph
1305                    .indexes
1306                    .retain(|idx| idx.index_id != drop_idx.id);
1307                MutationResult::Applied
1308            }
1309            Mutation::SearchPath(sp) => {
1310                self.snapshot_search_path();
1311                match &sp.target {
1312                    SearchPathTarget::Default => {
1313                        self.local.search_path = vec!["public".to_string()];
1314                    }
1315                    SearchPathTarget::Schemas(schemas) => {
1316                        self.local.search_path = schemas.clone();
1317                    }
1318                }
1319                MutationResult::Applied
1320            }
1321            Mutation::BeginTransaction => {
1322                self.local
1323                    .transactions
1324                    .push(TransactionFrame::new("transaction"));
1325                MutationResult::Applied
1326            }
1327            Mutation::CommitTransaction => {
1328                while self.local.transactions.pop().is_some() {}
1329                MutationResult::Applied
1330            }
1331            Mutation::RollbackTransaction => {
1332                while let Some(frame) = self.local.transactions.pop() {
1333                    self.rollback_frame(frame);
1334                }
1335                MutationResult::Applied
1336            }
1337            Mutation::RollbackToSavepoint(rts) => {
1338                let mut rolled_back = Vec::new();
1339                while let Some(frame) = self.local.transactions.last() {
1340                    if frame.name == rts.name {
1341                        break;
1342                    }
1343                    rolled_back.push(self.local.transactions.pop().unwrap());
1344                }
1345                if let Some(frame) = self.local.transactions.last_mut() {
1346                    let mut temp_frame = TransactionFrame::new(&frame.name);
1347                    while let Some(change) = frame.undo_log.pop() {
1348                        temp_frame.undo_log.push(change);
1349                    }
1350                    self.rollback_frame(temp_frame);
1351                }
1352                for frame in rolled_back.into_iter().rev() {
1353                    self.rollback_frame(frame);
1354                }
1355                MutationResult::Applied
1356            }
1357            Mutation::Savepoint(sp) => {
1358                self.local
1359                    .transactions
1360                    .push(TransactionFrame::new(sp.name.clone()));
1361                MutationResult::Applied
1362            }
1363            Mutation::ReleaseSavepoint(rsp) => {
1364                let mut rolled_back = Vec::new();
1365                while let Some(frame) = self.local.transactions.last() {
1366                    if frame.name == rsp.name {
1367                        break;
1368                    }
1369                    rolled_back.push(self.local.transactions.pop().unwrap());
1370                }
1371                if let Some(frame) = self.local.transactions.pop()
1372                    && let Some(outer) = self.local.transactions.last_mut()
1373                {
1374                    outer.undo_log.extend(frame.undo_log);
1375                }
1376                for frame in rolled_back.into_iter().rev() {
1377                    self.local.transactions.push(frame);
1378                }
1379                MutationResult::Applied
1380            }
1381            Mutation::Opaque(_) => {
1382                self.snapshot_confidence();
1383                self.local.confidence = Confidence::Tainted;
1384                MutationResult::Applied
1385            }
1386            Mutation::CreateFunction(f) => {
1387                self.snapshot_function(&f.id);
1388                self.snapshot_generation_counter();
1389                self.local.generation_counter += 1;
1390                let _generation = self.local.generation_counter;
1391
1392                let volatility = f
1393                    .options
1394                    .iter()
1395                    .find_map(|opt| {
1396                        if let crate::analysis::facts::FuncOptionFact::Volatility(v) = opt {
1397                            Some(match v {
1398                                crate::analysis::facts::VolatilityKind::Volatile => {
1399                                    crate::model::function::Volatility::Volatile
1400                                }
1401                                crate::analysis::facts::VolatilityKind::Stable => {
1402                                    crate::model::function::Volatility::Stable
1403                                }
1404                                crate::analysis::facts::VolatilityKind::Immutable => {
1405                                    crate::model::function::Volatility::Immutable
1406                                }
1407                            })
1408                        } else {
1409                            None
1410                        }
1411                    })
1412                    .unwrap_or(crate::model::function::Volatility::Volatile);
1413
1414                let security = f
1415                    .options
1416                    .iter()
1417                    .find_map(|opt| {
1418                        if let crate::analysis::facts::FuncOptionFact::Security(s) = opt {
1419                            Some(match s {
1420                                crate::analysis::facts::SecurityKind::Invoker => {
1421                                    crate::model::function::SecurityMode::Invoker
1422                                }
1423                                crate::analysis::facts::SecurityKind::Definer => {
1424                                    crate::model::function::SecurityMode::Definer
1425                                }
1426                            })
1427                        } else {
1428                            None
1429                        }
1430                    })
1431                    .unwrap_or(crate::model::function::SecurityMode::Invoker);
1432
1433                let language = f
1434                    .options
1435                    .iter()
1436                    .find_map(|opt| {
1437                        if let crate::analysis::facts::FuncOptionFact::Language(l) = opt {
1438                            Some(l.clone())
1439                        } else {
1440                            None
1441                        }
1442                    })
1443                    .unwrap_or_else(|| "sql".to_string());
1444
1445                self.local.functions.insert(
1446                    f.id.clone(),
1447                    crate::model::function::FunctionOverlay::Present(
1448                        crate::model::function::FunctionState {
1449                            id: f.id.clone(),
1450                            arg_types: f.params.iter().map(|p| p.ty.clone()).collect(),
1451                            return_type: f
1452                                .return_type
1453                                .as_ref()
1454                                .map(|rt| format!("{:?}", rt))
1455                                .unwrap_or_default(),
1456                            volatility,
1457                            language,
1458                            security,
1459                        },
1460                    ),
1461                );
1462                MutationResult::Applied
1463            }
1464            Mutation::AlterFunction(f) => {
1465                self.snapshot_function(&f.id);
1466                // Function generation tracking removed to match model definition
1467                MutationResult::Applied
1468            }
1469            Mutation::DropFunction(f) => {
1470                let mut any_applied = false;
1471                for sig in &f.signatures {
1472                    let sig_str = format!("{}({})", sig.name.name.resolve(), sig.params.join(","));
1473                    let schema = self.resolve_function_schema(&sig.name, &sig_str);
1474                    let id = ObjectId::new(schema, sig_str);
1475                    if !matches!(
1476                        self.local.functions.get(&id),
1477                        Some(crate::model::function::FunctionOverlay::Present(_))
1478                    ) {
1479                        if !f.if_exists {
1480                            self.local.confidence = Confidence::Tainted;
1481                            return MutationResult::Skipped;
1482                        }
1483                    } else {
1484                        any_applied = true;
1485                        self.snapshot_function(&id);
1486                        self.local
1487                            .functions
1488                            .insert(id, crate::model::function::FunctionOverlay::Dropped);
1489                    }
1490                }
1491                if any_applied {
1492                    MutationResult::Applied
1493                } else {
1494                    MutationResult::Skipped
1495                }
1496            }
1497            Mutation::CreateProcedure(p) => {
1498                self.snapshot_function(&p.id);
1499                self.snapshot_generation_counter();
1500                self.local.generation_counter += 1;
1501                let _generation = self.local.generation_counter;
1502
1503                self.local.functions.insert(
1504                    p.id.clone(),
1505                    crate::model::function::FunctionOverlay::Present(
1506                        crate::model::function::FunctionState {
1507                            id: p.id.clone(),
1508                            arg_types: p.params.iter().map(|p| p.ty.clone()).collect(),
1509                            return_type: "void".to_string(),
1510                            volatility: crate::model::function::Volatility::Volatile,
1511                            language: "sql".to_string(),
1512                            security: crate::model::function::SecurityMode::Invoker,
1513                        },
1514                    ),
1515                );
1516                MutationResult::Applied
1517            }
1518            Mutation::AlterProcedure(p) => {
1519                self.snapshot_function(&p.id);
1520                // No generation tracking in FunctionState
1521                MutationResult::Applied
1522            }
1523            Mutation::DropProcedure(p) => {
1524                let mut any_applied = false;
1525                for sig in &p.signatures {
1526                    let sig_str = format!("{}({})", sig.name.name.resolve(), sig.params.join(","));
1527                    let schema = self.resolve_function_schema(&sig.name, &sig_str);
1528                    let id = ObjectId::new(schema, sig_str);
1529                    if !matches!(
1530                        self.local.functions.get(&id),
1531                        Some(crate::model::function::FunctionOverlay::Present(_))
1532                    ) {
1533                        if !p.if_exists {
1534                            self.local.confidence = Confidence::Tainted;
1535                            return MutationResult::Skipped;
1536                        }
1537                    } else {
1538                        any_applied = true;
1539                        self.snapshot_function(&id);
1540                        self.local
1541                            .functions
1542                            .insert(id, crate::model::function::FunctionOverlay::Dropped);
1543                    }
1544                }
1545                if any_applied {
1546                    MutationResult::Applied
1547                } else {
1548                    MutationResult::Skipped
1549                }
1550            }
1551            Mutation::CreatePublication(p) => {
1552                self.snapshot_publication(&p.name);
1553                self.snapshot_generation_counter();
1554                self.local.generation_counter += 1;
1555                let generation = self.local.generation_counter;
1556
1557                self.local.publications.insert(
1558                    p.name.clone(),
1559                    crate::model::replication::PublicationOverlay::Present(
1560                        crate::model::replication::PublicationState {
1561                            name: p.name.clone(),
1562                            scope: p.scope.clone(),
1563                            params: p.params.clone(),
1564                            generation,
1565                        },
1566                    ),
1567                );
1568
1569                if let crate::analysis::facts::PublicationScope::Explicit(objects) = &p.scope {
1570                    self.snapshot_publication_graph_full();
1571                    for obj in objects {
1572                        if let crate::analysis::facts::PublicationObjectFact::Table {
1573                            name, ..
1574                        } = obj
1575                        {
1576                            let table_id = self.resolve_relation_id(name);
1577                            self.local
1578                                .graph
1579                                .publication_dependencies
1580                                .push(PublicationEdge {
1581                                    publication_name: p.name.clone(),
1582                                    table_id,
1583                                });
1584                        }
1585                    }
1586                }
1587                MutationResult::Applied
1588            }
1589            Mutation::AlterPublication(p) => {
1590                self.snapshot_publication(&p.name);
1591                if !self.local.publications.contains_key(&p.name) {
1592                    self.local.confidence = Confidence::Tainted;
1593                    return MutationResult::Skipped;
1594                }
1595                self.snapshot_generation_counter();
1596                self.local.generation_counter += 1;
1597                let new_gen = self.local.generation_counter;
1598
1599                if let Some(crate::model::replication::PublicationOverlay::Present(publ)) =
1600                    self.local.publications.get_mut(&p.name)
1601                {
1602                    publ.generation = new_gen;
1603                }
1604                MutationResult::Applied
1605            }
1606            Mutation::DropPublication(p) => {
1607                for name in &p.names {
1608                    self.snapshot_publication(name);
1609                    if !p.if_exists && !self.local.publications.contains_key(name) {
1610                        self.local.confidence = Confidence::Tainted;
1611                        return MutationResult::Skipped;
1612                    }
1613                    self.local.publications.insert(
1614                        name.clone(),
1615                        crate::model::replication::PublicationOverlay::Dropped,
1616                    );
1617                }
1618                self.snapshot_publication_graph_full();
1619                self.local
1620                    .graph
1621                    .publication_dependencies
1622                    .retain(|edge| !p.names.contains(&edge.publication_name));
1623                MutationResult::Applied
1624            }
1625            Mutation::CreateSubscription(s) => {
1626                let name = s.name.clone().unwrap_or_else(|| "unnamed_sub".into());
1627                self.snapshot_subscription(&name);
1628                self.snapshot_generation_counter();
1629                self.local.generation_counter += 1;
1630                let generation = self.local.generation_counter;
1631
1632                self.local.subscriptions.insert(
1633                    name.clone(),
1634                    crate::model::replication::SubscriptionOverlay::Present(
1635                        crate::model::replication::SubscriptionState {
1636                            name,
1637                            connection: s.connection.clone(),
1638                            publications: s.publications.clone(),
1639                            params: s.params.clone(),
1640                            generation,
1641                        },
1642                    ),
1643                );
1644                MutationResult::Applied
1645            }
1646            Mutation::AlterSubscription(s) => {
1647                self.snapshot_subscription(&s.name);
1648                if !self.local.subscriptions.contains_key(&s.name) {
1649                    self.local.confidence = Confidence::Tainted;
1650                    return MutationResult::Skipped;
1651                }
1652                self.snapshot_generation_counter();
1653                self.local.generation_counter += 1;
1654                let new_gen = self.local.generation_counter;
1655
1656                if let Some(crate::model::replication::SubscriptionOverlay::Present(sub)) =
1657                    self.local.subscriptions.get_mut(&s.name)
1658                {
1659                    sub.generation = new_gen;
1660                }
1661                MutationResult::Applied
1662            }
1663            Mutation::DropSubscription(s) => {
1664                self.snapshot_subscription(&s.name);
1665                if !s.if_exists && !self.local.subscriptions.contains_key(&s.name) {
1666                    self.local.confidence = Confidence::Tainted;
1667                    return MutationResult::Skipped;
1668                }
1669                self.local.subscriptions.insert(
1670                    s.name.clone(),
1671                    crate::model::replication::SubscriptionOverlay::Dropped,
1672                );
1673                MutationResult::Applied
1674            }
1675            Mutation::CreateRole(r) => {
1676                let role_id = ObjectId::new("", &r.name);
1677                self.snapshot_role(&role_id);
1678                self.snapshot_generation_counter();
1679                self.local.generation_counter += 1;
1680                let _generation = self.local.generation_counter;
1681
1682                self.local.roles.insert(
1683                    role_id.clone(),
1684                    crate::model::role::RoleOverlay::Present(crate::model::role::RoleState {
1685                        id: role_id,
1686                        can_login: true,
1687                        is_superuser: false,
1688                        member_of: Vec::new(),
1689                        granted_privileges: Vec::new(),
1690                    }),
1691                );
1692                MutationResult::Applied
1693            }
1694            Mutation::AlterRole(r) => {
1695                if let Some(role_id) = Self::resolve_role_name(&r.name, &self.local.current_role) {
1696                    self.snapshot_role(&role_id);
1697                    if !self.local.roles.contains_key(&role_id) {
1698                        self.local.confidence = Confidence::Tainted;
1699                        return MutationResult::Skipped;
1700                    }
1701                    self.snapshot_generation_counter();
1702                    self.local.generation_counter += 1;
1703                    let _new_gen = self.local.generation_counter;
1704
1705                    if let Some(crate::model::role::RoleOverlay::Present(_role)) =
1706                        self.local.roles.get_mut(&role_id)
1707                    {
1708                        // No further action as fields have been simplified
1709                    }
1710                    MutationResult::Applied
1711                } else {
1712                    MutationResult::Skipped
1713                }
1714            }
1715            Mutation::DropRole(r) => {
1716                for name in &r.names {
1717                    if let Some(role_id) = Self::resolve_role_name(
1718                        &crate::analysis::facts::RoleFact::Named {
1719                            name: name.clone(),
1720                            via_legacy_group_syntax: false,
1721                        },
1722                        &self.local.current_role,
1723                    ) {
1724                        self.snapshot_role(&role_id);
1725                        if !r.if_exists && !self.local.roles.contains_key(&role_id) {
1726                            self.local.confidence = Confidence::Tainted;
1727
1728                            return MutationResult::Skipped;
1729                        }
1730                        self.local
1731                            .roles
1732                            .insert(role_id, crate::model::role::RoleOverlay::Dropped);
1733                    }
1734                }
1735                MutationResult::Applied
1736            }
1737            Mutation::Grant(grant) => {
1738                let privileges = Self::resolve_grant_privileges(&grant.privileges);
1739                let grantees = &grant.grantees;
1740                match &grant.target {
1741                    crate::analysis::mutations::ResolvedGrantTarget::Tables(ids) => {
1742                        for id in ids {
1743                            self.apply_grant_to_relation(id, &privileges, grantees);
1744                        }
1745                    }
1746                    crate::analysis::mutations::ResolvedGrantTarget::AllTablesInSchema(schemas) => {
1747                        let target_ids: Vec<ObjectId> = self
1748                            .local
1749                            .relations
1750                            .keys()
1751                            .filter(|id| schemas.contains(&id.schema))
1752                            .cloned()
1753                            .collect();
1754                        for id in &target_ids {
1755                            self.apply_grant_to_relation(id, &privileges, grantees);
1756                        }
1757                    }
1758                }
1759                MutationResult::Applied
1760            }
1761            Mutation::Revoke(revoke) => {
1762                let privileges = Self::resolve_grant_privileges(&revoke.privileges);
1763                let revokees = &revoke.revokees;
1764                match &revoke.target {
1765                    crate::analysis::mutations::ResolvedGrantTarget::Tables(ids) => {
1766                        for id in ids {
1767                            self.apply_revoke_to_relation(id, &privileges, revokees);
1768                        }
1769                    }
1770                    crate::analysis::mutations::ResolvedGrantTarget::AllTablesInSchema(schemas) => {
1771                        let target_ids: Vec<ObjectId> = self
1772                            .local
1773                            .relations
1774                            .keys()
1775                            .filter(|id| schemas.contains(&id.schema))
1776                            .cloned()
1777                            .collect();
1778                        for id in &target_ids {
1779                            self.apply_revoke_to_relation(id, &privileges, revokees);
1780                        }
1781                    }
1782                }
1783                MutationResult::Applied
1784            }
1785            Mutation::CreateDatabase(_) => MutationResult::Applied,
1786            Mutation::AlterDatabase(_) => MutationResult::Applied,
1787            Mutation::DropDatabase(_) => MutationResult::Applied,
1788            Mutation::Vacuum { .. } => MutationResult::Applied,
1789        }
1790    }
1791
1792    fn snapshot_relation(&mut self, id: &ObjectId) {
1793        if let Some(frame) = self.local.transactions.last_mut() {
1794            let previous = self.local.relations.get(id).cloned();
1795            frame.undo_log.push(StateChange::RelationSnapshot {
1796                id: id.clone(),
1797                previous: Box::new(previous),
1798            });
1799        }
1800    }
1801
1802    fn snapshot_type(&mut self, id: &ObjectId) {
1803        if let Some(frame) = self.local.transactions.last_mut() {
1804            let previous = self.local.types.get(id).cloned();
1805            frame.undo_log.push(StateChange::TypeSnapshot {
1806                id: id.clone(),
1807                previous,
1808            });
1809        }
1810    }
1811
1812    fn snapshot_sequence(&mut self, id: &ObjectId) {
1813        if let Some(frame) = self.local.transactions.last_mut() {
1814            let previous = self.local.sequences.get(id).cloned();
1815            frame.undo_log.push(StateChange::SequenceSnapshot {
1816                id: id.clone(),
1817                previous,
1818            });
1819        }
1820    }
1821
1822    fn snapshot_function(&mut self, id: &ObjectId) {
1823        if let Some(frame) = self.local.transactions.last_mut() {
1824            let previous = self.local.functions.get(id).cloned();
1825            frame.undo_log.push(StateChange::FunctionSnapshot {
1826                id: id.clone(),
1827                previous,
1828            });
1829        }
1830    }
1831
1832    fn snapshot_publication(&mut self, name: &str) {
1833        if let Some(frame) = self.local.transactions.last_mut() {
1834            let previous = self.local.publications.get(name).cloned();
1835            frame.undo_log.push(StateChange::PublicationSnapshot {
1836                id: ObjectId::new("", name),
1837                previous,
1838            });
1839        }
1840    }
1841
1842    fn snapshot_subscription(&mut self, name: &str) {
1843        if let Some(frame) = self.local.transactions.last_mut() {
1844            let previous = self.local.subscriptions.get(name).cloned();
1845            frame.undo_log.push(StateChange::SubscriptionSnapshot {
1846                id: ObjectId::new("", name),
1847                previous,
1848            });
1849        }
1850    }
1851
1852    fn snapshot_role(&mut self, id: &ObjectId) {
1853        if let Some(frame) = self.local.transactions.last_mut() {
1854            let previous = self.local.roles.get(id).cloned();
1855            frame.undo_log.push(StateChange::RoleSnapshot {
1856                id: id.clone(),
1857                previous,
1858            });
1859        }
1860    }
1861
1862    fn snapshot_trigger(&mut self, id: &ObjectId) {
1863        if let Some(frame) = self.local.transactions.last_mut() {
1864            let previous = self.local.triggers.get(id).cloned();
1865            frame.undo_log.push(StateChange::TriggerSnapshot {
1866                id: id.clone(),
1867                previous,
1868            });
1869        }
1870    }
1871
1872    fn snapshot_trigger_graph_full(&mut self) {
1873        if let Some(frame) = self.local.transactions.last_mut() {
1874            frame.undo_log.push(StateChange::TriggerGraphSnapshot {
1875                previous: self.local.graph.trigger_dependencies.clone(),
1876            });
1877        }
1878    }
1879
1880    fn snapshot_publication_graph_full(&mut self) {
1881        if let Some(frame) = self.local.transactions.last_mut() {
1882            frame.undo_log.push(StateChange::PublicationGraphSnapshot {
1883                previous: self.local.graph.publication_dependencies.clone(),
1884            });
1885        }
1886    }
1887
1888    #[allow(dead_code)]
1889    fn snapshot_current_role(&mut self) {
1890        if let Some(frame) = self.local.transactions.last_mut() {
1891            frame.undo_log.push(StateChange::CurrentRoleSnapshot {
1892                previous: self.local.current_role.clone(),
1893            });
1894        }
1895    }
1896
1897    fn snapshot_search_path(&mut self) {
1898        if let Some(frame) = self.local.transactions.last_mut() {
1899            frame.undo_log.push(StateChange::SearchPathSnapshot {
1900                previous: self.local.search_path.clone(),
1901            });
1902        }
1903    }
1904
1905    fn snapshot_generation_counter(&mut self) {
1906        if let Some(frame) = self.local.transactions.last_mut() {
1907            frame.undo_log.push(StateChange::GenerationCounterSnapshot {
1908                previous: self.local.generation_counter,
1909            });
1910        }
1911    }
1912
1913    #[allow(dead_code)]
1914    fn snapshot_pending_validation(&mut self) {
1915        if let Some(frame) = self.local.transactions.last_mut() {
1916            frame.undo_log.push(StateChange::PendingValidationSnapshot {
1917                previous: self.local.pending_validation.clone(),
1918            });
1919        }
1920    }
1921
1922    fn snapshot_confidence(&mut self) {
1923        if let Some(frame) = self.local.transactions.last_mut() {
1924            frame.undo_log.push(StateChange::ConfidenceSnapshot {
1925                previous: self.local.confidence.clone(),
1926            });
1927        }
1928    }
1929
1930    fn snapshot_fk_graph(&mut self) {
1931        if let Some(frame) = self.local.transactions.last_mut() {
1932            frame.undo_log.push(StateChange::FkGraphLengthMarker {
1933                len: self.local.graph.foreign_keys.len(),
1934            });
1935        }
1936    }
1937
1938    fn snapshot_fk_graph_full(&mut self) {
1939        if let Some(frame) = self.local.transactions.last_mut() {
1940            frame.undo_log.push(StateChange::FkGraphSnapshot {
1941                previous: self.local.graph.foreign_keys.clone(),
1942            });
1943        }
1944    }
1945
1946    fn snapshot_view_graph(&mut self) {
1947        if let Some(frame) = self.local.transactions.last_mut() {
1948            frame.undo_log.push(StateChange::ViewGraphLengthMarker {
1949                len: self.local.graph.views.len(),
1950            });
1951        }
1952    }
1953
1954    fn snapshot_view_graph_full(&mut self) {
1955        if let Some(frame) = self.local.transactions.last_mut() {
1956            frame.undo_log.push(StateChange::ViewGraphSnapshot {
1957                previous: self.local.graph.views.clone(),
1958            });
1959        }
1960    }
1961
1962    fn snapshot_index_graph(&mut self) {
1963        if let Some(frame) = self.local.transactions.last_mut() {
1964            frame.undo_log.push(StateChange::IndexGraphLengthMarker {
1965                len: self.local.graph.indexes.len(),
1966            });
1967        }
1968    }
1969
1970    fn snapshot_index_graph_full(&mut self) {
1971        if let Some(frame) = self.local.transactions.last_mut() {
1972            frame.undo_log.push(StateChange::IndexGraphSnapshot {
1973                previous: self.local.graph.indexes.clone(),
1974            });
1975        }
1976    }
1977
1978    fn snapshot_partition_graph(&mut self) {
1979        if let Some(frame) = self.local.transactions.last_mut() {
1980            frame.undo_log.push(StateChange::PartitionGraphMarker {
1981                len: self.local.graph.partitions.len(),
1982            });
1983        }
1984    }
1985
1986    fn snapshot_partition_graph_full(&mut self) {
1987        if let Some(frame) = self.local.transactions.last_mut() {
1988            frame.undo_log.push(StateChange::PartitionGraphSnapshot {
1989                previous: self.local.graph.partitions.clone(),
1990            });
1991        }
1992    }
1993
1994    fn snapshot_sequence_graph(&mut self) {
1995        if let Some(frame) = self.local.transactions.last_mut() {
1996            frame.undo_log.push(StateChange::SequenceGraphLengthMarker {
1997                len: self.local.graph.sequences.len(),
1998            });
1999        }
2000    }
2001
2002    fn snapshot_sequence_graph_full(&mut self) {
2003        if let Some(frame) = self.local.transactions.last_mut() {
2004            frame.undo_log.push(StateChange::SequenceGraphSnapshot {
2005                previous: self.local.graph.sequences.clone(),
2006            });
2007        }
2008    }
2009
2010    fn snapshot_column_graph(&mut self) {
2011        if let Some(frame) = self.local.transactions.last_mut() {
2012            frame.undo_log.push(StateChange::ColumnGraphLengthMarker {
2013                len: self.local.graph.column_dependencies.len(),
2014            });
2015        }
2016    }
2017
2018    fn snapshot_column_graph_full(&mut self) {
2019        if let Some(frame) = self.local.transactions.last_mut() {
2020            frame.undo_log.push(StateChange::ColumnGraphSnapshot {
2021                previous: self.local.graph.column_dependencies.clone(),
2022            });
2023        }
2024    }
2025
2026    fn snapshot_rename_graph(&mut self) {
2027        if let Some(frame) = self.local.transactions.last_mut() {
2028            frame.undo_log.push(StateChange::RenameGraphLengthMarker {
2029                len: self.local.graph.renames.len(),
2030            });
2031        }
2032    }
2033
2034    fn snapshot_rename_graph_full(&mut self) {
2035        if let Some(frame) = self.local.transactions.last_mut() {
2036            frame.undo_log.push(StateChange::RenameGraphSnapshot {
2037                previous: self.local.graph.renames.clone(),
2038            });
2039        }
2040    }
2041
2042    fn rollback_frame(&mut self, mut frame: TransactionFrame) {
2043        while let Some(change) = frame.undo_log.pop() {
2044            match change {
2045                StateChange::RelationSnapshot { id, previous } => {
2046                    if let Some(prev) = *previous {
2047                        self.local.relations.insert(id, prev);
2048                    } else {
2049                        self.local.relations.remove(&id);
2050                    }
2051                }
2052                StateChange::TypeSnapshot { id, previous } => {
2053                    if let Some(prev) = previous {
2054                        self.local.types.insert(id, prev);
2055                    } else {
2056                        self.local.types.remove(&id);
2057                    }
2058                }
2059                StateChange::SequenceSnapshot { id, previous } => {
2060                    if let Some(prev) = previous {
2061                        self.local.sequences.insert(id, prev);
2062                    } else {
2063                        self.local.sequences.remove(&id);
2064                    }
2065                }
2066                StateChange::FunctionSnapshot { id, previous } => {
2067                    if let Some(prev) = previous {
2068                        self.local.functions.insert(id, prev);
2069                    } else {
2070                        self.local.functions.remove(&id);
2071                    }
2072                }
2073                StateChange::PublicationSnapshot { id, previous } => {
2074                    if let Some(prev) = previous {
2075                        self.local.publications.insert(id.name, prev);
2076                    } else {
2077                        self.local.publications.remove(&id.name);
2078                    }
2079                }
2080                StateChange::SubscriptionSnapshot { id, previous } => {
2081                    if let Some(prev) = previous {
2082                        self.local.subscriptions.insert(id.name, prev);
2083                    } else {
2084                        self.local.subscriptions.remove(&id.name);
2085                    }
2086                }
2087                StateChange::RoleSnapshot { id, previous } => {
2088                    if let Some(prev) = previous {
2089                        self.local.roles.insert(id, prev);
2090                    } else {
2091                        self.local.roles.remove(&id);
2092                    }
2093                }
2094                StateChange::TriggerSnapshot { id, previous } => {
2095                    if let Some(prev) = previous {
2096                        self.local.triggers.insert(id, prev);
2097                    } else {
2098                        self.local.triggers.remove(&id);
2099                    }
2100                }
2101                StateChange::TriggerGraphSnapshot { previous } => {
2102                    self.local.graph.trigger_dependencies = previous;
2103                }
2104                StateChange::PublicationGraphSnapshot { previous } => {
2105                    self.local.graph.publication_dependencies = previous;
2106                }
2107                StateChange::CurrentRoleSnapshot { previous } => {
2108                    self.local.current_role = previous;
2109                }
2110                StateChange::SearchPathSnapshot { previous } => {
2111                    self.local.search_path = previous;
2112                }
2113                StateChange::GenerationCounterSnapshot { previous } => {
2114                    self.local.generation_counter = previous;
2115                }
2116                StateChange::PendingValidationSnapshot { previous } => {
2117                    self.local.pending_validation = previous;
2118                }
2119                StateChange::ConfidenceSnapshot { previous } => {
2120                    self.local.confidence = previous;
2121                }
2122                StateChange::FkGraphLengthMarker { len } => {
2123                    self.local.graph.foreign_keys.truncate(len);
2124                }
2125                StateChange::FkGraphSnapshot { previous } => {
2126                    self.local.graph.foreign_keys = previous;
2127                }
2128                StateChange::ViewGraphLengthMarker { len } => {
2129                    self.local.graph.views.truncate(len);
2130                }
2131                StateChange::ViewGraphSnapshot { previous } => {
2132                    self.local.graph.views = previous;
2133                }
2134                StateChange::IndexGraphLengthMarker { len } => {
2135                    self.local.graph.indexes.truncate(len);
2136                }
2137                StateChange::IndexGraphSnapshot { previous } => {
2138                    self.local.graph.indexes = previous;
2139                }
2140                StateChange::PartitionGraphMarker { len } => {
2141                    self.local.graph.partitions.truncate(len);
2142                }
2143                StateChange::PartitionGraphSnapshot { previous } => {
2144                    self.local.graph.partitions = previous;
2145                }
2146                StateChange::SequenceGraphLengthMarker { len } => {
2147                    self.local.graph.sequences.truncate(len);
2148                }
2149                StateChange::SequenceGraphSnapshot { previous } => {
2150                    self.local.graph.sequences = previous;
2151                }
2152                StateChange::RenameGraphLengthMarker { len } => {
2153                    self.local.graph.renames.truncate(len);
2154                }
2155                StateChange::RenameGraphSnapshot { previous } => {
2156                    self.local.graph.renames = previous;
2157                }
2158                StateChange::ColumnGraphLengthMarker { len } => {
2159                    self.local.graph.column_dependencies.truncate(len);
2160                }
2161                StateChange::ColumnGraphSnapshot { previous } => {
2162                    self.local.graph.column_dependencies = previous;
2163                }
2164            }
2165        }
2166    }
2167}