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::{DependencyEdge, DependencyGraph, DependencyKind};
4use crate::analysis::mutations::{
5    AlterTableActionMutation, AlterTypeActionMutation, Mutation, PersistenceMutation,
6};
7use crate::analysis::transaction::{NamespaceSnapshot, StateChange, TransactionFrame};
8use crate::ast::identifiers::ObjectId;
9use crate::db::cache::DbCache;
10use crate::model::constraint::{ConstraintKind, ConstraintState};
11pub use crate::model::relation::RelationOverlay;
12use crate::model::relation::{ColumnAction, Persistence, Privilege, RelationKind, RelationState};
13use crate::model::schema::SchemaOverlay;
14use crate::model::sequence::{SequenceKind, SequenceOverlay, SequenceState};
15use crate::model::trigger::TriggerOverlay;
16use crate::model::types::{TypeKind, TypeOverlay, TypeState};
17use std::collections::{HashMap, HashSet};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum Confidence {
21    Exact,
22    Tainted,
23}
24
25#[derive(Debug, PartialEq, Eq)]
26pub enum MutationResult {
27    Applied,
28    Skipped,
29    /// PostgreSQL did not execute this statement because an earlier statement
30    /// aborted the active transaction.
31    NotExecuted,
32    Conflict {
33        reason: String,
34    },
35}
36
37#[derive(Debug, Default, Clone)]
38pub struct CascadeResult {
39    pub dropped_relations: HashSet<ObjectId>,
40    pub dropped_indexes: HashSet<ObjectId>,
41    pub dropped_constraints: HashSet<(ObjectId, String)>,
42}
43
44#[derive(Clone)]
45pub struct LocalState {
46    pub schemas: HashMap<String, SchemaOverlay>,
47    pub relations: HashMap<ObjectId, RelationOverlay>,
48    pub types: HashMap<ObjectId, TypeOverlay>,
49    pub functions: HashMap<ObjectId, crate::model::function::FunctionOverlay>,
50    pub sequences: HashMap<ObjectId, SequenceOverlay>,
51    pub publications: HashMap<String, crate::model::replication::PublicationOverlay>,
52    pub subscriptions: HashMap<String, crate::model::replication::SubscriptionOverlay>,
53    pub roles: HashMap<ObjectId, crate::model::role::RoleOverlay>,
54    pub triggers: HashMap<ObjectId, TriggerOverlay>,
55    pub constraints: HashMap<(ObjectId, String), ConstraintState>,
56    pub graph: DependencyGraph,
57    pub search_path: Vec<String>,
58    pub default_search_path: Vec<String>,
59    pub search_path_template: Vec<String>,
60    pub default_search_path_template: Vec<String>,
61    /// Role currently active for this session context (updated by SET ROLE /
62    /// SET SESSION AUTHORIZATION). Begins equal to `session_role`.
63    pub current_role: String,
64    /// Whether `current_role` is statically known. False when no V5 cache was
65    /// loaded and no SET ROLE statement has been processed yet.
66    pub current_role_known: bool,
67    /// Effective role setting that survives transaction commit. A LOCAL role
68    /// change updates `current_role` without changing this value.
69    pub persistent_current_role: String,
70    pub persistent_current_role_known: bool,
71    /// The session-level role as captured from the cache's `source_role`. This
72    /// is the value `SET ROLE NONE` / `SET SESSION AUTHORIZATION DEFAULT`
73    /// reverts to.
74    pub session_role: String,
75    /// Whether `session_role` is statically known (mirrors `current_role_known`
76    /// at baseline; a SET SESSION AUTHORIZATION updates this too).
77    pub session_role_known: bool,
78    /// Session authorization that survives transaction commit.
79    pub persistent_session_role: String,
80    pub persistent_session_role_known: bool,
81    /// Login identity restored by `SET SESSION AUTHORIZATION DEFAULT`.
82    pub authenticated_role: String,
83    pub authenticated_role_known: bool,
84    /// Whether the cache contains a complete PostgreSQL role catalog.
85    pub roles_known: bool,
86    pub confidence: Confidence,
87    pub transactions: Vec<TransactionFrame>,
88    pub transaction_aborted: bool,
89    pub pending_validation: HashSet<(ObjectId, String)>,
90    pub generation_counter: u64,
91}
92
93#[derive(Clone, Debug)]
94pub struct PreState {
95    pub relations: HashMap<ObjectId, crate::model::relation::RelationState>,
96    pub functions: HashMap<ObjectId, crate::model::function::FunctionState>,
97    pub roles: HashMap<ObjectId, crate::model::role::RoleState>,
98    pub publications: HashMap<String, crate::model::replication::PublicationState>,
99    pub subscriptions: HashMap<String, crate::model::replication::SubscriptionState>,
100    pub sequences: HashMap<ObjectId, crate::model::sequence::SequenceState>,
101    pub types: HashMap<ObjectId, crate::model::types::TypeState>,
102    pub indexes: Vec<crate::analysis::graph::DependencyEdge>,
103}
104
105#[derive(Clone)]
106pub struct AnalysisState {
107    pub pg_version_num: Option<u32>,
108    /// Whether the initial cache was loaded from a real cache file. An empty
109    /// cache can be a valid baseline for an empty database, so availability
110    /// must not be inferred from the number of modeled objects.
111    pub baseline_available: bool,
112    /// `None` means the cache covered all non-system schemas. A populated set
113    /// records an explicitly scoped sync, for which objects outside the set
114    /// are unknown rather than known absent.
115    pub baseline_schemas: Option<HashSet<String>>,
116    pub baseline_relations: HashSet<ObjectId>,
117    pub baseline_indexes: HashSet<ObjectId>,
118    pub baseline_foreign_keys: HashSet<(ObjectId, String)>,
119    pub baseline_fk_dependencies: HashSet<ObjectId>,
120    pub baseline_sequences: HashSet<ObjectId>,
121    pub local: LocalState,
122}
123
124impl AnalysisState {
125    fn trigger_key(table_id: &ObjectId, name: &str) -> ObjectId {
126        // PostgreSQL identifiers cannot contain NUL, so this is an unambiguous
127        // internal composite key while keeping the public cache representation
128        // as the trigger's actual name.
129        ObjectId::new(&table_id.schema, format!("{}\0{name}", table_id.name))
130    }
131
132    pub fn new(cache: DbCache) -> Self {
133        Self::with_baseline(cache, true)
134    }
135
136    pub fn with_baseline(cache: DbCache, baseline_available: bool) -> Self {
137        let default_search_path = cache.search_path.clone();
138        let default_search_path_template = if cache.metadata.schemas.is_none() {
139            cache
140                .metadata
141                .source_search_path
142                .clone()
143                .unwrap_or_else(|| default_search_path.clone())
144        } else {
145            default_search_path.clone()
146        };
147        let current_role_known = cache.metadata.source_role.is_some();
148        let current_role = cache
149            .metadata
150            .source_role
151            .clone()
152            .unwrap_or_else(|| "postgres".to_string());
153        let session_role_known = cache.metadata.source_session_role.is_some();
154        let session_role = cache
155            .metadata
156            .source_session_role
157            .clone()
158            .unwrap_or_else(|| current_role.clone());
159        let authenticated_role = session_role.clone();
160        let authenticated_role_known = session_role_known;
161        let persistent_current_role = current_role.clone();
162        let persistent_current_role_known = current_role_known;
163        let persistent_session_role = session_role.clone();
164        let persistent_session_role_known = session_role_known;
165        let roles_known = cache.metadata.source_session_role.is_some();
166        let baseline_schemas = cache
167            .metadata
168            .schemas
169            .as_ref()
170            .map(|schemas| schemas.iter().cloned().collect());
171        let mut relations: HashMap<ObjectId, RelationOverlay> = HashMap::new();
172        let mut baseline_relations = HashSet::new();
173        let mut baseline_indexes = HashSet::new();
174        let mut baseline_foreign_keys = HashSet::new();
175        let mut baseline_fk_dependencies = HashSet::new();
176        let mut triggers = HashMap::new();
177        let mut constraints = HashMap::new();
178        let mut types = HashMap::new();
179        let mut graph = DependencyGraph::new();
180
181        let mut schemas: HashMap<String, SchemaOverlay> = cache
182            .schemas
183            .iter()
184            .map(|(name, schema)| (name.clone(), SchemaOverlay::Present(schema.clone())))
185            .collect();
186        // Effective cached search-path entries and modeled objects are direct
187        // evidence that their namespaces existed at synchronization time.
188        // This also keeps programmatically assembled V5 caches internally
189        // consistent without treating unrelated out-of-scope schemas as
190        // authoritative catalogs.
191        let inferred_schema_owner = ObjectId::new(
192            "",
193            cache.metadata.source_role.as_deref().unwrap_or("postgres"),
194        );
195        for name in cache
196            .relations
197            .keys()
198            .map(|id| &id.schema)
199            .chain(cache.types.keys().map(|id| &id.schema))
200            .chain(cache.functions.keys().map(|id| &id.schema))
201            .chain(cache.sequences.keys().map(|id| &id.schema))
202        {
203            schemas.entry(name.clone()).or_insert_with(|| {
204                SchemaOverlay::Present(crate::model::schema::SchemaState {
205                    name: name.clone(),
206                    owner: inferred_schema_owner.clone(),
207                    generation: 0,
208                })
209            });
210        }
211        if cache.schemas.is_empty() && cache.metadata.schemas.is_none() {
212            for name in &cache.search_path {
213                schemas.entry(name.clone()).or_insert_with(|| {
214                    SchemaOverlay::Present(crate::model::schema::SchemaState {
215                        name: name.clone(),
216                        owner: inferred_schema_owner.clone(),
217                        generation: 0,
218                    })
219                });
220            }
221        }
222
223        let sequences = cache
224            .sequences
225            .iter()
226            .map(|(id, sequence)| (id.clone(), SequenceOverlay::Present(sequence.clone())))
227            .collect();
228        let baseline_sequences = cache.sequences.keys().cloned().collect();
229        for sequence in cache.sequences.values() {
230            if let Some((table, column)) = &sequence.owned_by {
231                graph.edges.push(DependencyEdge::new(
232                    sequence.id.clone(),
233                    table.clone(),
234                    DependencyKind::SequenceOwnedBy {
235                        column: column.clone(),
236                    },
237                ));
238            }
239        }
240
241        for (id, rel_state) in cache.baseline_relations() {
242            if rel_state.is_fk_dependency {
243                baseline_fk_dependencies.insert(id.clone());
244            }
245            relations.insert(id.clone(), RelationOverlay::Present(rel_state.clone()));
246            baseline_relations.insert(id.clone());
247        }
248
249        for (id, type_state) in &cache.types {
250            types.insert(id.clone(), TypeOverlay::Present(type_state.clone()));
251        }
252        let type_catalog = types.clone();
253        for overlay in relations.values_mut() {
254            if let RelationOverlay::Present(relation) = overlay {
255                for column in &mut relation.columns {
256                    column.type_id = column.data_type.as_deref().and_then(|raw| {
257                        Self::resolve_type_reference_from_catalog(
258                            raw,
259                            &type_catalog,
260                            &default_search_path,
261                        )
262                    });
263                }
264            }
265        }
266        for overlay in types.values_mut() {
267            if let TypeOverlay::Present(TypeState {
268                kind:
269                    TypeKind::Domain {
270                        base_type,
271                        base_type_id,
272                    },
273                ..
274            }) = overlay
275            {
276                *base_type_id = Self::resolve_type_reference_from_catalog(
277                    base_type,
278                    &type_catalog,
279                    &default_search_path,
280                );
281            }
282        }
283        for fk in cache.foreign_keys {
284            baseline_foreign_keys.insert((fk.from_table.clone(), fk.constraint_name.clone()));
285            graph.edges.push(DependencyEdge::new(
286                fk.from_table,
287                fk.to_table,
288                DependencyKind::ForeignKey {
289                    constraint_name: Some(fk.constraint_name),
290                    from_columns: Vec::new(),
291                    to_columns: Vec::new(),
292                    from_generation: 0,
293                },
294            ));
295        }
296
297        for idx in cache.indexes {
298            // BUG-008: index ObjectIds go into baseline_indexes, not baseline_relations
299            baseline_indexes.insert(idx.index_id.clone());
300            graph.edges.push(DependencyEdge::new(
301                idx.index_id,
302                idx.table_id,
303                DependencyKind::IndexOnRelation {
304                    using_method: None,
305                    has_predicate: false,
306                    is_concurrent: false,
307                    is_unique: false,
308                    eligibility_known: false,
309                },
310            ));
311        }
312
313        for dependency in cache.dependencies {
314            if dependency.deptype != "view" {
315                continue;
316            }
317            let (Some(obj_schema), Some(obj_name), Some(ref_schema), Some(ref_name)) = (
318                dependency.obj_schema,
319                dependency.obj_name,
320                dependency.ref_schema,
321                dependency.ref_name,
322            ) else {
323                continue;
324            };
325            let dependent = ObjectId::new(obj_schema, obj_name);
326            let referenced = ObjectId::new(ref_schema, ref_name);
327            // Older caches created on PostgreSQL 14/15 can contain an
328            // internal pg_rewrite self-edge for a view. Ignore it while
329            // loading so upgrading safe-migrate does not require a re-sync to
330            // restore a meaningful dependency graph.
331            if dependent == referenced {
332                continue;
333            }
334            let is_view = relations.get(&dependent).is_some_and(|relation| {
335                matches!(
336                    relation,
337                    RelationOverlay::Present(state)
338                        if matches!(
339                            state.kind,
340                            crate::model::relation::RelationKind::View
341                                | crate::model::relation::RelationKind::MaterializedView
342                        )
343                )
344            });
345            if is_view && relations.contains_key(&referenced) {
346                graph.edges.push(DependencyEdge::new(
347                    dependent,
348                    referenced,
349                    DependencyKind::ViewDependency { view_generation: 0 },
350                ));
351            }
352        }
353
354        for constraint in cache.constraints {
355            constraints.insert(
356                (constraint.table_id.clone(), constraint.name.clone()),
357                constraint,
358            );
359        }
360
361        for t in cache.triggers {
362            let trigger_key = Self::trigger_key(&t.table_id, &t.trigger_id.name);
363            triggers.insert(
364                trigger_key.clone(),
365                TriggerOverlay::Present(crate::model::trigger::TriggerState {
366                    name: t.trigger_id.name.clone(),
367                    id: trigger_key.clone(),
368                    table_id: t.table_id.clone(),
369                    enabled_mode: t.enabled_mode,
370                    generation: 0,
371                }),
372            );
373            graph.edges.push(DependencyEdge::new(
374                trigger_key.clone(),
375                t.table_id,
376                DependencyKind::TriggerOnTable {
377                    trigger_id: trigger_key,
378                    function_id: t.function_id,
379                },
380            ));
381        }
382
383        let mut functions: HashMap<ObjectId, crate::model::function::FunctionOverlay> =
384            HashMap::new();
385        for (id, func_state) in &cache.functions {
386            functions.insert(
387                id.clone(),
388                crate::model::function::FunctionOverlay::Present(func_state.clone()),
389            );
390        }
391        for overlay in functions.values_mut() {
392            if let crate::model::function::FunctionOverlay::Present(function) = overlay {
393                function.arg_type_ids = function
394                    .arg_types
395                    .iter()
396                    .map(|raw| {
397                        Self::resolve_type_reference_from_catalog(
398                            raw,
399                            &type_catalog,
400                            &default_search_path,
401                        )
402                    })
403                    .collect();
404                function.return_type_id = Self::resolve_type_reference_from_catalog(
405                    &function.return_type,
406                    &type_catalog,
407                    &default_search_path,
408                );
409            }
410        }
411
412        let mut state = Self {
413            pg_version_num: cache.pg_version_num,
414            baseline_available,
415            baseline_schemas,
416            baseline_relations,
417            baseline_indexes,
418            baseline_foreign_keys,
419            baseline_fk_dependencies,
420            baseline_sequences,
421            local: LocalState {
422                schemas,
423                relations,
424                types,
425                functions,
426                sequences,
427                publications: HashMap::new(),
428                subscriptions: HashMap::new(),
429                roles: cache
430                    .roles
431                    .into_iter()
432                    .map(|(id, role)| (id, crate::model::role::RoleOverlay::Present(role)))
433                    .collect(),
434                triggers,
435                constraints,
436                graph,
437                search_path: default_search_path.clone(),
438                default_search_path,
439                search_path_template: default_search_path_template.clone(),
440                default_search_path_template,
441                current_role,
442                current_role_known,
443                persistent_current_role,
444                persistent_current_role_known,
445                session_role,
446                session_role_known,
447                persistent_session_role,
448                persistent_session_role_known,
449                authenticated_role,
450                authenticated_role_known,
451                roles_known,
452                confidence: Confidence::Exact,
453                transactions: Vec::new(),
454                transaction_aborted: false,
455                pending_validation: HashSet::new(),
456                generation_counter: 0,
457            },
458        };
459        state.refresh_role_sensitive_search_path();
460        state.local.default_search_path = state.local.search_path.clone();
461        state
462    }
463
464    pub fn get_relation(&self, id: &ObjectId) -> Option<&RelationOverlay> {
465        self.local.relations.get(id)
466    }
467
468    pub fn resolve_function_schema(
469        &self,
470        name: &crate::ast::identifiers::QualifiedName,
471        sig_str: &str,
472    ) -> String {
473        if let Some(schema) = &name.schema {
474            return schema.resolve();
475        }
476        for schema in &self.local.search_path {
477            let candidate = ObjectId::new(schema.clone(), sig_str.to_string());
478            if self.local.functions.contains_key(&candidate) {
479                return schema.clone();
480            }
481        }
482        self.local
483            .search_path
484            .first()
485            .cloned()
486            .unwrap_or_else(|| "public".to_string())
487    }
488
489    pub fn resolve_relation_id(&self, name: &crate::ast::identifiers::QualifiedName) -> ObjectId {
490        if let Some(schema) = &name.schema {
491            return ObjectId::new(schema.resolve(), name.name.resolve());
492        }
493        let resolved_name = name.name.resolve();
494        for schema in &self.local.search_path {
495            let mut candidate = ObjectId::new(schema.clone(), resolved_name.clone());
496            if self.local.relations.contains_key(&candidate) {
497                candidate.inferred_schema = true;
498                return candidate;
499            }
500        }
501        let schema = self
502            .local
503            .search_path
504            .first()
505            .cloned()
506            .unwrap_or_else(|| "public".to_string());
507        let mut id = ObjectId::new(schema, resolved_name);
508        id.inferred_schema = true;
509        id
510    }
511
512    pub fn relation_is_present(&self, id: &ObjectId) -> bool {
513        matches!(
514            self.local.relations.get(id),
515            Some(RelationOverlay::Present(_))
516        )
517    }
518
519    /// Returns whether a cache-backed absence is authoritative for an object.
520    /// A scoped cache only establishes absence in the schemas it actually
521    /// synchronized.
522    pub fn baseline_covers_object(&self, id: &ObjectId) -> bool {
523        self.baseline_schemas
524            .as_ref()
525            .is_none_or(|schemas| schemas.contains(&id.schema))
526    }
527
528    pub fn baseline_scope_omits_displayed_object<'a>(
529        &self,
530        object_name: &'a str,
531    ) -> Option<&'a str> {
532        let schemas = self.baseline_schemas.as_ref()?;
533        let (schema, _) = object_name.split_once('.')?;
534        (!schemas.contains(schema)).then_some(schema)
535    }
536
537    fn sequence_is_present(&self, id: &ObjectId) -> bool {
538        matches!(
539            self.local.sequences.get(id),
540            Some(SequenceOverlay::Present(_))
541        )
542    }
543
544    fn type_is_present(&self, id: &ObjectId) -> bool {
545        matches!(self.local.types.get(id), Some(TypeOverlay::Present(_)))
546    }
547
548    fn resolve_type_reference_from_catalog(
549        raw: &str,
550        types: &HashMap<ObjectId, TypeOverlay>,
551        search_path: &[String],
552    ) -> Option<ObjectId> {
553        let (schema, name) = Self::parse_type_reference(raw)?;
554        if let Some(schema) = schema {
555            let candidate = ObjectId::new(schema, name);
556            return matches!(types.get(&candidate), Some(TypeOverlay::Present(_)))
557                .then_some(candidate);
558        }
559        search_path.iter().find_map(|schema| {
560            let candidate = ObjectId::new(schema, &name);
561            matches!(types.get(&candidate), Some(TypeOverlay::Present(_))).then_some(candidate)
562        })
563    }
564
565    /// Parse a SQL type name exactly enough to resolve modeled named types.
566    /// Quoted identifiers retain their case, unquoted identifiers fold to
567    /// lowercase, and any number of array suffixes are ignored for lookup.
568    fn parse_type_reference(raw: &str) -> Option<(Option<String>, String)> {
569        let mut token = raw.trim();
570        while let Some(without_array) = token.strip_suffix("[]") {
571            token = without_array.trim_end();
572        }
573
574        let mut parts = Vec::new();
575        let mut current = String::new();
576        let mut quoted = false;
577        let mut part_is_quoted = false;
578        let mut chars = token.chars().peekable();
579        while let Some(character) = chars.next() {
580            match character {
581                '"' if quoted && chars.peek() == Some(&'"') => {
582                    current.push('"');
583                    chars.next();
584                }
585                '"' => {
586                    quoted = !quoted;
587                    part_is_quoted = true;
588                }
589                '.' if !quoted => {
590                    parts.push(Self::resolve_type_identifier(&current, part_is_quoted)?);
591                    current.clear();
592                    part_is_quoted = false;
593                }
594                character if !quoted && character.is_whitespace() => {}
595                character => current.push(character),
596            }
597        }
598        if quoted {
599            return None;
600        }
601        parts.push(Self::resolve_type_identifier(&current, part_is_quoted)?);
602        match parts.as_slice() {
603            [name] => Some((None, name.clone())),
604            [schema, name] => Some((Some(schema.clone()), name.clone())),
605            _ => None,
606        }
607    }
608
609    fn resolve_type_identifier(identifier: &str, quoted: bool) -> Option<String> {
610        (!identifier.is_empty()).then(|| {
611            if quoted {
612                identifier.to_string()
613            } else {
614                identifier.to_lowercase()
615            }
616        })
617    }
618
619    fn resolve_type_reference(&self, raw: &str) -> Option<ObjectId> {
620        Self::resolve_type_reference_from_catalog(raw, &self.local.types, &self.local.search_path)
621    }
622
623    fn type_reference_name(id: &ObjectId, qualified: bool) -> String {
624        let quote = |identifier: &str| {
625            let unquoted = identifier
626                .chars()
627                .enumerate()
628                .all(|(index, character)| match index {
629                    0 => character.is_ascii_lowercase() || character == '_',
630                    _ => {
631                        character.is_ascii_lowercase()
632                            || character.is_ascii_digit()
633                            || character == '_'
634                            || character == '$'
635                    }
636                });
637            if unquoted {
638                identifier.to_string()
639            } else {
640                format!("\"{}\"", identifier.replace('"', "\"\""))
641            }
642        };
643
644        if qualified {
645            format!("{}.{}", quote(&id.schema), quote(&id.name))
646        } else {
647            quote(&id.name)
648        }
649    }
650
651    fn remapped_type_display(raw: &str, new_id: &ObjectId, schema_changed: bool) -> String {
652        let suffix = raw.find('[').map(|index| &raw[index..]).unwrap_or("");
653        format!(
654            "{}{}",
655            Self::type_reference_name(new_id, schema_changed),
656            suffix
657        )
658    }
659
660    fn index_is_present(&self, id: &ObjectId) -> bool {
661        self.local.graph.edges.iter().any(|edge| {
662            matches!(edge.kind, DependencyKind::IndexOnRelation { .. }) && edge.dependent == *id
663        })
664    }
665
666    fn next_generated_constraint_name(
667        &self,
668        table: &ObjectId,
669        name1: &str,
670        name2: Option<&str>,
671        label: &str,
672    ) -> String {
673        (0..)
674            .map(|suffix| {
675                let label = if suffix == 0 {
676                    label.to_string()
677                } else {
678                    format!("{label}{suffix}")
679                };
680                Self::postgres_object_name(name1, name2, &label)
681            })
682            .find(|candidate| {
683                !self
684                    .local
685                    .constraints
686                    .contains_key(&(table.clone(), candidate.clone()))
687            })
688            .expect("constraint suffix space is unbounded")
689    }
690
691    fn postgres_object_name(name1: &str, name2: Option<&str>, label: &str) -> String {
692        const MAX_IDENTIFIER_BYTES: usize = 63;
693
694        fn truncate(value: &str, max_bytes: usize) -> &str {
695            let mut end = max_bytes.min(value.len());
696            while !value.is_char_boundary(end) {
697                end -= 1;
698            }
699            &value[..end]
700        }
701
702        let separators = usize::from(name2.is_some()) + 1;
703        let available = MAX_IDENTIFIER_BYTES.saturating_sub(label.len() + separators);
704        let mut name1_bytes = name1.len();
705        let mut name2_bytes = name2.map_or(0, str::len);
706        while name1_bytes + name2_bytes > available {
707            if name1_bytes > name2_bytes {
708                name1_bytes -= 1;
709            } else {
710                name2_bytes -= 1;
711            }
712        }
713
714        let name1 = truncate(name1, name1_bytes);
715        match name2 {
716            Some(name2) => format!("{name1}_{}_{}", truncate(name2, name2_bytes), label),
717            None => format!("{name1}_{label}"),
718        }
719    }
720
721    fn relation_namespace_is_taken(&self, id: &ObjectId) -> bool {
722        self.relation_is_present(id)
723            || self.sequence_is_present(id)
724            || self.index_is_present(id)
725            || self.type_is_present(id)
726    }
727
728    fn next_implicit_sequence_id(
729        &self,
730        table: &ObjectId,
731        column: &str,
732        reserved: &HashSet<ObjectId>,
733    ) -> ObjectId {
734        (0..)
735            .map(|suffix| {
736                let label = if suffix == 0 {
737                    "seq".to_string()
738                } else {
739                    format!("seq{suffix}")
740                };
741                ObjectId::new(
742                    &table.schema,
743                    Self::postgres_object_name(&table.name, Some(column), &label),
744                )
745            })
746            .find(|candidate| {
747                !reserved.contains(candidate) && !self.relation_namespace_is_taken(candidate)
748            })
749            .expect("implicit sequence suffix space is unbounded")
750    }
751
752    fn sequence_nextval_default(id: &ObjectId) -> crate::analysis::expr_ir::ExprIr {
753        crate::analysis::expr_ir::ExprIr::FunctionCall {
754            name: "nextval".to_string(),
755            args: vec![crate::analysis::expr_ir::ExprIr::Literal(format!(
756                "{}.{}",
757                id.schema, id.name
758            ))],
759        }
760    }
761
762    pub fn column_was_added_in_transaction(&self, table_id: &ObjectId, column: &str) -> bool {
763        if self.local.transactions.is_empty() {
764            return false;
765        }
766
767        // Search from the oldest transaction frame to the newest
768        for frame in &self.local.transactions {
769            for change in &frame.undo_log {
770                if let StateChange::RelationSnapshot { id, previous } = change
771                    && id == table_id
772                {
773                    match previous.as_ref() {
774                        None | Some(RelationOverlay::Dropped) => {
775                            return true;
776                        }
777                        Some(RelationOverlay::Present(r)) => {
778                            let col_existed = r.columns.iter().any(|c| c.name == column);
779                            return !col_existed;
780                        }
781                    }
782                }
783            }
784        }
785        false
786    }
787
788    pub fn capture_pre_state(&self) -> PreState {
789        let mut relations = HashMap::new();
790        for (id, overlay) in &self.local.relations {
791            if let RelationOverlay::Present(s) = overlay {
792                relations.insert(id.clone(), s.clone());
793            }
794        }
795
796        let mut functions = HashMap::new();
797        for (id, overlay) in &self.local.functions {
798            if let crate::model::function::FunctionOverlay::Present(s) = overlay {
799                functions.insert(id.clone(), s.clone());
800            }
801        }
802
803        let mut roles = HashMap::new();
804        for (name, overlay) in &self.local.roles {
805            if let crate::model::role::RoleOverlay::Present(s) = overlay {
806                roles.insert(name.clone(), s.clone());
807            }
808        }
809
810        let mut publications = HashMap::new();
811        for (name, overlay) in &self.local.publications {
812            if let crate::model::replication::PublicationOverlay::Present(s) = overlay {
813                publications.insert(name.clone(), s.clone());
814            }
815        }
816
817        let mut subscriptions = HashMap::new();
818        for (name, overlay) in &self.local.subscriptions {
819            if let crate::model::replication::SubscriptionOverlay::Present(s) = overlay {
820                subscriptions.insert(name.clone(), s.clone());
821            }
822        }
823
824        let mut sequences = HashMap::new();
825        for (id, overlay) in &self.local.sequences {
826            if let SequenceOverlay::Present(s) = overlay {
827                sequences.insert(id.clone(), s.clone());
828            }
829        }
830
831        let mut types = HashMap::new();
832        for (id, overlay) in &self.local.types {
833            if let TypeOverlay::Present(s) = overlay {
834                types.insert(id.clone(), s.clone());
835            }
836        }
837
838        let indexes = self
839            .local
840            .graph
841            .edges
842            .iter()
843            .filter(|e| matches!(e.kind, DependencyKind::IndexOnRelation { .. }))
844            .cloned()
845            .collect();
846
847        PreState {
848            relations,
849            functions,
850            roles,
851            publications,
852            subscriptions,
853            sequences,
854            types,
855            indexes,
856        }
857    }
858
859    pub fn get_cascade_closure(&self, target_oid: &ObjectId) -> CascadeResult {
860        let mut result = CascadeResult::default();
861        let mut visited = HashSet::new();
862        self.walk_cascade(target_oid, &mut visited, &mut result);
863        result
864    }
865
866    fn walk_cascade(
867        &self,
868        current: &ObjectId,
869        visited: &mut HashSet<ObjectId>,
870        result: &mut CascadeResult,
871    ) {
872        let resolved_current = self.local.graph.resolve_rename(current).clone();
873
874        if !visited.insert(resolved_current.clone()) {
875            return;
876        }
877
878        result.dropped_relations.insert(resolved_current.clone());
879
880        for edge in &self.local.graph.edges {
881            match &edge.kind {
882                DependencyKind::ViewDependency { .. } => {
883                    if self.local.graph.resolve_rename(&edge.referenced) == &resolved_current {
884                        let resolved_view_id =
885                            self.local.graph.resolve_rename(&edge.dependent).clone();
886                        if !visited.contains(&resolved_view_id) {
887                            self.walk_cascade(&resolved_view_id, visited, result);
888                        }
889                    }
890                }
891                DependencyKind::IndexOnRelation { .. } => {
892                    if self.local.graph.resolve_rename(&edge.referenced) == &resolved_current {
893                        result
894                            .dropped_indexes
895                            .insert(self.local.graph.resolve_rename(&edge.dependent).clone());
896                    }
897                }
898                DependencyKind::ForeignKey {
899                    constraint_name, ..
900                } => {
901                    if self.local.graph.resolve_rename(&edge.referenced) == &resolved_current
902                        && let Some(cname) = constraint_name
903                    {
904                        result.dropped_constraints.insert((
905                            self.local.graph.resolve_rename(&edge.dependent).clone(),
906                            cname.clone(),
907                        ));
908                    }
909                }
910                DependencyKind::PartitionOf
911                    if self.local.graph.resolve_rename(&edge.referenced) == &resolved_current =>
912                {
913                    let resolved_child = self.local.graph.resolve_rename(&edge.dependent).clone();
914                    if !visited.contains(&resolved_child) {
915                        self.walk_cascade(&resolved_child, visited, result);
916                    }
917                }
918                _ => {}
919            }
920        }
921    }
922
923    fn resolve_grant_privileges(
924        spec: &crate::analysis::facts::PrivilegeSpec,
925    ) -> HashSet<Privilege> {
926        match spec {
927            crate::analysis::facts::PrivilegeSpec::All => vec![
928                Privilege::Select,
929                Privilege::Insert,
930                Privilege::Update,
931                Privilege::Delete,
932                Privilege::Truncate,
933                Privilege::References,
934                Privilege::Trigger,
935            ]
936            .into_iter()
937            .collect(),
938            crate::analysis::facts::PrivilegeSpec::List(list) => list
939                .iter()
940                .filter_map(|p| match p {
941                    crate::analysis::facts::PrivilegeFact::Select => Some(Privilege::Select),
942                    crate::analysis::facts::PrivilegeFact::Insert => Some(Privilege::Insert),
943                    crate::analysis::facts::PrivilegeFact::Update => Some(Privilege::Update),
944                    crate::analysis::facts::PrivilegeFact::Delete => Some(Privilege::Delete),
945                    crate::analysis::facts::PrivilegeFact::Truncate => Some(Privilege::Truncate),
946                    crate::analysis::facts::PrivilegeFact::References => {
947                        Some(Privilege::References)
948                    }
949                    crate::analysis::facts::PrivilegeFact::Trigger => Some(Privilege::Trigger),
950                    _ => None,
951                })
952                .collect(),
953        }
954    }
955
956    fn resolve_role_name(
957        role: &crate::analysis::facts::RoleFact,
958        current_role: &str,
959        session_role: &str,
960    ) -> Option<ObjectId> {
961        let name = match role {
962            crate::analysis::facts::RoleFact::Named { name, .. } => Some(name.clone()),
963            crate::analysis::facts::RoleFact::CurrentUser
964            | crate::analysis::facts::RoleFact::CurrentRole => Some(current_role.to_string()),
965            crate::analysis::facts::RoleFact::SessionUser => Some(session_role.to_string()),
966            crate::analysis::facts::RoleFact::Unknown => None,
967        }?;
968        Some(ObjectId::new("", name))
969    }
970
971    fn role_fact_identity(
972        &self,
973        role: &crate::analysis::facts::RoleFact,
974    ) -> Option<(String, bool)> {
975        match role {
976            crate::analysis::facts::RoleFact::Named { name, .. } => Some((name.clone(), true)),
977            crate::analysis::facts::RoleFact::CurrentUser
978            | crate::analysis::facts::RoleFact::CurrentRole => Some((
979                self.local.current_role.clone(),
980                self.local.current_role_known,
981            )),
982            crate::analysis::facts::RoleFact::SessionUser => Some((
983                self.local.session_role.clone(),
984                self.local.session_role_known,
985            )),
986            crate::analysis::facts::RoleFact::Unknown => None,
987        }
988    }
989
990    fn present_role(&self, name: &str) -> Option<&crate::model::role::RoleState> {
991        match self.local.roles.get(&ObjectId::new("", name)) {
992            Some(crate::model::role::RoleOverlay::Present(role)) => Some(role),
993            _ => None,
994        }
995    }
996
997    fn can_set_role_to(&self, target: &str) -> Option<bool> {
998        if !self.local.roles_known || !self.local.session_role_known {
999            return None;
1000        }
1001        if self.present_role(target).is_none() {
1002            return Some(false);
1003        }
1004        if self.local.session_role == target {
1005            return Some(true);
1006        }
1007        let session = self.present_role(&self.local.session_role)?;
1008        if session.is_superuser {
1009            return Some(true);
1010        }
1011
1012        let mut pending = session.can_set_role_to.clone();
1013        let mut visited = HashSet::new();
1014        while let Some(role_id) = pending.pop() {
1015            if !visited.insert(role_id.clone()) {
1016                continue;
1017            }
1018            if role_id.name == target {
1019                return Some(true);
1020            }
1021            if let Some(role) = self.present_role(&role_id.name) {
1022                pending.extend(role.can_set_role_to.iter().cloned());
1023            }
1024        }
1025        Some(false)
1026    }
1027
1028    fn can_set_session_authorization_to(&self, target: &str) -> Option<bool> {
1029        if !self.local.roles_known || !self.local.authenticated_role_known {
1030            return None;
1031        }
1032        if self.present_role(target).is_none() {
1033            return Some(false);
1034        }
1035        if self.local.authenticated_role == target {
1036            return Some(true);
1037        }
1038        Some(
1039            self.present_role(&self.local.authenticated_role)
1040                .is_some_and(|role| role.is_superuser),
1041        )
1042    }
1043
1044    fn schema_is_present(&self, name: &str) -> bool {
1045        matches!(
1046            self.local.schemas.get(name),
1047            Some(SchemaOverlay::Present(_))
1048        )
1049    }
1050
1051    fn schema_absence_is_authoritative(&self, name: &str) -> bool {
1052        if matches!(self.local.schemas.get(name), Some(SchemaOverlay::Dropped)) {
1053            return true;
1054        }
1055        self.baseline_available
1056            && self
1057                .baseline_schemas
1058                .as_ref()
1059                .is_none_or(|schemas| schemas.contains(name))
1060    }
1061
1062    fn refresh_role_sensitive_search_path(&mut self) {
1063        let template = self.local.search_path_template.clone();
1064        let mut effective = Vec::new();
1065        for entry in template {
1066            let schema = if entry == "$user" {
1067                if self.local.current_role_known {
1068                    self.local.current_role.clone()
1069                } else {
1070                    self.local.confidence = Confidence::Tainted;
1071                    continue;
1072                }
1073            } else {
1074                entry
1075            };
1076            if self.schema_is_present(&schema) {
1077                if !effective.contains(&schema) {
1078                    effective.push(schema);
1079                }
1080            } else if !self.schema_absence_is_authoritative(&schema) {
1081                self.local.confidence = Confidence::Tainted;
1082                if !effective.contains(&schema) {
1083                    effective.push(schema);
1084                }
1085            }
1086        }
1087        self.local.search_path = effective;
1088    }
1089
1090    fn remap_schema_id(id: &mut ObjectId, old_name: &str, new_name: &str) {
1091        if id.schema == old_name {
1092            id.schema = new_name.to_string();
1093        }
1094    }
1095
1096    fn rename_schema_namespace(&mut self, old_name: &str, new_name: &str) {
1097        self.snapshot_namespace();
1098
1099        let mut aliases = Vec::new();
1100        let mut relations = HashMap::new();
1101        for (mut id, mut overlay) in std::mem::take(&mut self.local.relations) {
1102            let old_id = id.clone();
1103            Self::remap_schema_id(&mut id, old_name, new_name);
1104            if let RelationOverlay::Present(state) = &mut overlay {
1105                Self::remap_schema_id(&mut state.id, old_name, new_name);
1106            }
1107            if id != old_id {
1108                aliases.push((old_id, id.clone()));
1109            }
1110            relations.insert(id, overlay);
1111        }
1112        self.local.relations = relations;
1113
1114        let mut types = HashMap::new();
1115        for (mut id, mut overlay) in std::mem::take(&mut self.local.types) {
1116            let old_id = id.clone();
1117            Self::remap_schema_id(&mut id, old_name, new_name);
1118            if let TypeOverlay::Present(state) = &mut overlay {
1119                Self::remap_schema_id(&mut state.id, old_name, new_name);
1120            }
1121            if id != old_id {
1122                aliases.push((old_id, id.clone()));
1123            }
1124            types.insert(id, overlay);
1125        }
1126        self.local.types = types;
1127
1128        let mut functions = HashMap::new();
1129        for (mut id, mut overlay) in std::mem::take(&mut self.local.functions) {
1130            let old_id = id.clone();
1131            Self::remap_schema_id(&mut id, old_name, new_name);
1132            if let crate::model::function::FunctionOverlay::Present(state) = &mut overlay {
1133                Self::remap_schema_id(&mut state.id, old_name, new_name);
1134            }
1135            if id != old_id {
1136                aliases.push((old_id, id.clone()));
1137            }
1138            functions.insert(id, overlay);
1139        }
1140        self.local.functions = functions;
1141
1142        let mut sequences = HashMap::new();
1143        for (mut id, mut overlay) in std::mem::take(&mut self.local.sequences) {
1144            let old_id = id.clone();
1145            Self::remap_schema_id(&mut id, old_name, new_name);
1146            if let SequenceOverlay::Present(state) = &mut overlay {
1147                Self::remap_schema_id(&mut state.id, old_name, new_name);
1148                if let Some((table, _)) = &mut state.owned_by {
1149                    Self::remap_schema_id(table, old_name, new_name);
1150                }
1151            }
1152            if id != old_id {
1153                aliases.push((old_id, id.clone()));
1154            }
1155            sequences.insert(id, overlay);
1156        }
1157        self.local.sequences = sequences;
1158
1159        let mut triggers = HashMap::new();
1160        for (mut id, mut overlay) in std::mem::take(&mut self.local.triggers) {
1161            let old_id = id.clone();
1162            Self::remap_schema_id(&mut id, old_name, new_name);
1163            if let TriggerOverlay::Present(state) = &mut overlay {
1164                Self::remap_schema_id(&mut state.id, old_name, new_name);
1165                Self::remap_schema_id(&mut state.table_id, old_name, new_name);
1166            }
1167            if id != old_id {
1168                aliases.push((old_id, id.clone()));
1169            }
1170            triggers.insert(id, overlay);
1171        }
1172        self.local.triggers = triggers;
1173
1174        for overlay in self.local.publications.values_mut() {
1175            let crate::model::replication::PublicationOverlay::Present(publication) = overlay
1176            else {
1177                continue;
1178            };
1179            let crate::analysis::facts::PublicationScope::Explicit(objects) =
1180                &mut publication.scope
1181            else {
1182                continue;
1183            };
1184            for object in objects {
1185                match object {
1186                    crate::analysis::facts::PublicationObjectFact::Table { name, .. } => {
1187                        if name
1188                            .schema
1189                            .as_ref()
1190                            .is_some_and(|schema| schema.resolve() == old_name)
1191                        {
1192                            name.schema = Some(crate::ast::identifiers::Ident::new(new_name, true));
1193                        }
1194                    }
1195                    crate::analysis::facts::PublicationObjectFact::SchemaTables {
1196                        schema, ..
1197                    } if schema == old_name => *schema = new_name.to_string(),
1198                    _ => {}
1199                }
1200            }
1201        }
1202
1203        self.local.constraints = std::mem::take(&mut self.local.constraints)
1204            .into_iter()
1205            .map(|((mut table, name), mut constraint)| {
1206                Self::remap_schema_id(&mut table, old_name, new_name);
1207                Self::remap_schema_id(&mut constraint.table_id, old_name, new_name);
1208                ((table, name), constraint)
1209            })
1210            .collect();
1211        self.local.pending_validation = std::mem::take(&mut self.local.pending_validation)
1212            .into_iter()
1213            .map(|(mut table, name)| {
1214                Self::remap_schema_id(&mut table, old_name, new_name);
1215                (table, name)
1216            })
1217            .collect();
1218
1219        for edge in &mut self.local.graph.edges {
1220            Self::remap_schema_id(&mut edge.dependent, old_name, new_name);
1221            Self::remap_schema_id(&mut edge.referenced, old_name, new_name);
1222            if let DependencyKind::TriggerOnTable {
1223                trigger_id,
1224                function_id,
1225            } = &mut edge.kind
1226            {
1227                Self::remap_schema_id(trigger_id, old_name, new_name);
1228                Self::remap_schema_id(function_id, old_name, new_name);
1229            }
1230        }
1231        for (old_id, new_id) in aliases {
1232            self.local.graph.edges.push(DependencyEdge::new(
1233                old_id,
1234                new_id,
1235                DependencyKind::RenameTo,
1236            ));
1237        }
1238
1239        let remap_set = |set: &mut HashSet<ObjectId>| {
1240            *set = std::mem::take(set)
1241                .into_iter()
1242                .map(|mut id| {
1243                    Self::remap_schema_id(&mut id, old_name, new_name);
1244                    id
1245                })
1246                .collect();
1247        };
1248        remap_set(&mut self.baseline_relations);
1249        remap_set(&mut self.baseline_indexes);
1250        remap_set(&mut self.baseline_fk_dependencies);
1251        remap_set(&mut self.baseline_sequences);
1252        self.baseline_foreign_keys = std::mem::take(&mut self.baseline_foreign_keys)
1253            .into_iter()
1254            .map(|(mut table, name)| {
1255                Self::remap_schema_id(&mut table, old_name, new_name);
1256                (table, name)
1257            })
1258            .collect();
1259
1260        if let Some(SchemaOverlay::Present(mut schema)) = self.local.schemas.remove(old_name) {
1261            schema.name = new_name.to_string();
1262            self.local
1263                .schemas
1264                .insert(new_name.to_string(), SchemaOverlay::Present(schema));
1265        }
1266        self.refresh_role_sensitive_search_path();
1267    }
1268
1269    fn restore_persistent_role_context(&mut self) {
1270        self.local.current_role = self.local.persistent_current_role.clone();
1271        self.local.current_role_known = self.local.persistent_current_role_known;
1272        self.local.session_role = self.local.persistent_session_role.clone();
1273        self.local.session_role_known = self.local.persistent_session_role_known;
1274        self.refresh_role_sensitive_search_path();
1275    }
1276
1277    fn apply_grant_to_relation(
1278        &mut self,
1279        id: &ObjectId,
1280        privileges: &HashSet<Privilege>,
1281        grantees: &[crate::analysis::facts::RoleFact],
1282    ) {
1283        self.snapshot_relation(id);
1284        if let Some(RelationOverlay::Present(rel)) = self.local.relations.get_mut(id) {
1285            for grantee in grantees {
1286                if let Some(role_id) = Self::resolve_role_name(
1287                    grantee,
1288                    &self.local.current_role,
1289                    &self.local.session_role,
1290                ) {
1291                    rel.privileges.grant(role_id, privileges.clone());
1292                }
1293            }
1294        }
1295    }
1296
1297    fn apply_revoke_to_relation(
1298        &mut self,
1299        id: &ObjectId,
1300        privileges: &HashSet<Privilege>,
1301        revokees: &[crate::analysis::facts::RoleFact],
1302    ) {
1303        self.snapshot_relation(id);
1304        if let Some(RelationOverlay::Present(rel)) = self.local.relations.get_mut(id) {
1305            for revokee in revokees {
1306                if let Some(role_id) = Self::resolve_role_name(
1307                    revokee,
1308                    &self.local.current_role,
1309                    &self.local.session_role,
1310                ) {
1311                    rel.privileges.revoke(&role_id, privileges);
1312                }
1313            }
1314        }
1315    }
1316
1317    pub fn apply(
1318        &mut self,
1319        mutation: &Mutation,
1320        precomputed_cascade: Option<&CascadeResult>,
1321    ) -> MutationResult {
1322        if self.local.transaction_aborted
1323            && !matches!(
1324                mutation,
1325                Mutation::CommitTransaction
1326                    | Mutation::CommitAndChain
1327                    | Mutation::RollbackTransaction
1328                    | Mutation::RollbackAndChain
1329                    | Mutation::RollbackToSavepoint(_)
1330            )
1331        {
1332            return MutationResult::NotExecuted;
1333        }
1334
1335        let result = self.apply_inner(mutation, precomputed_cascade);
1336        if matches!(result, MutationResult::Conflict { .. }) && !self.local.transactions.is_empty()
1337        {
1338            self.local.transaction_aborted = true;
1339        }
1340        result
1341    }
1342
1343    fn apply_inner(
1344        &mut self,
1345        mutation: &Mutation,
1346        precomputed_cascade: Option<&CascadeResult>,
1347    ) -> MutationResult {
1348        match mutation {
1349            Mutation::CreateSchema(create_schema) => {
1350                if self.schema_is_present(&create_schema.name) {
1351                    return if create_schema.if_not_exists {
1352                        MutationResult::Skipped
1353                    } else {
1354                        MutationResult::Conflict {
1355                            reason: format!("schema '{}' already exists", create_schema.name),
1356                        }
1357                    };
1358                }
1359                let (owner_name, owner_known) = match &create_schema.authorization {
1360                    Some(role) => match self.role_fact_identity(role) {
1361                        Some(identity) => identity,
1362                        None => {
1363                            self.snapshot_confidence();
1364                            self.local.confidence = Confidence::Tainted;
1365                            (self.local.current_role.clone(), false)
1366                        }
1367                    },
1368                    None => (
1369                        self.local.current_role.clone(),
1370                        self.local.current_role_known,
1371                    ),
1372                };
1373                if owner_known && self.local.roles_known && self.present_role(&owner_name).is_none()
1374                {
1375                    return MutationResult::Conflict {
1376                        reason: format!("role '{}' does not exist", owner_name),
1377                    };
1378                }
1379                if !owner_known || !self.local.roles_known {
1380                    self.snapshot_confidence();
1381                    self.local.confidence = Confidence::Tainted;
1382                }
1383                self.snapshot_generation_counter();
1384                self.local.generation_counter += 1;
1385                let generation = self.local.generation_counter;
1386                self.snapshot_schema(&create_schema.name);
1387                self.local.schemas.insert(
1388                    create_schema.name.clone(),
1389                    SchemaOverlay::Present(crate::model::schema::SchemaState {
1390                        name: create_schema.name.clone(),
1391                        owner: ObjectId::new("", owner_name),
1392                        generation,
1393                    }),
1394                );
1395                self.snapshot_search_path();
1396                self.refresh_role_sensitive_search_path();
1397                MutationResult::Applied
1398            }
1399            Mutation::AlterSchema(alter_schema) => match alter_schema {
1400                crate::analysis::mutations::AlterSchemaMutation::OwnerTo { name, new_owner } => {
1401                    if !self.schema_is_present(name) {
1402                        if self.schema_absence_is_authoritative(name) {
1403                            return MutationResult::Conflict {
1404                                reason: format!("schema '{}' does not exist", name),
1405                            };
1406                        }
1407                        self.snapshot_confidence();
1408                        self.local.confidence = Confidence::Tainted;
1409                        return MutationResult::Skipped;
1410                    }
1411                    let Some((owner_name, owner_known)) = self.role_fact_identity(new_owner) else {
1412                        self.snapshot_confidence();
1413                        self.local.confidence = Confidence::Tainted;
1414                        return MutationResult::Skipped;
1415                    };
1416                    if owner_known
1417                        && self.local.roles_known
1418                        && self.present_role(&owner_name).is_none()
1419                    {
1420                        return MutationResult::Conflict {
1421                            reason: format!("role '{}' does not exist", owner_name),
1422                        };
1423                    }
1424                    if !owner_known || !self.local.roles_known {
1425                        self.snapshot_confidence();
1426                        self.local.confidence = Confidence::Tainted;
1427                    }
1428                    self.snapshot_schema(name);
1429                    if let Some(SchemaOverlay::Present(schema)) = self.local.schemas.get_mut(name) {
1430                        schema.owner = ObjectId::new("", owner_name);
1431                    }
1432                    MutationResult::Applied
1433                }
1434                crate::analysis::mutations::AlterSchemaMutation::Rename { old_name, new_name } => {
1435                    if !self.schema_is_present(old_name) {
1436                        if !self.schema_absence_is_authoritative(old_name) {
1437                            self.snapshot_confidence();
1438                            self.local.confidence = Confidence::Tainted;
1439                            return MutationResult::Skipped;
1440                        }
1441                        return MutationResult::Conflict {
1442                            reason: format!("schema '{}' does not exist", old_name),
1443                        };
1444                    }
1445                    if self.schema_is_present(new_name) {
1446                        return MutationResult::Conflict {
1447                            reason: format!("schema '{}' already exists", new_name),
1448                        };
1449                    }
1450                    if !self.schema_absence_is_authoritative(new_name) {
1451                        self.snapshot_confidence();
1452                        self.local.confidence = Confidence::Tainted;
1453                    }
1454                    self.snapshot_search_path();
1455                    self.rename_schema_namespace(old_name, new_name);
1456                    MutationResult::Applied
1457                }
1458            },
1459            Mutation::DropSchema(drop_schema) => {
1460                for name in &drop_schema.names {
1461                    if !self.schema_is_present(name) && self.schema_absence_is_authoritative(name) {
1462                        if !drop_schema.if_exists {
1463                            return MutationResult::Conflict {
1464                                reason: format!("schema '{}' does not exist", name),
1465                            };
1466                        }
1467                    } else if !self.schema_is_present(name) {
1468                        self.snapshot_confidence();
1469                        self.local.confidence = Confidence::Tainted;
1470                    }
1471                }
1472                let present_names: Vec<String> = drop_schema
1473                    .names
1474                    .iter()
1475                    .filter(|name| self.schema_is_present(name))
1476                    .cloned()
1477                    .collect();
1478                if present_names.is_empty() {
1479                    return MutationResult::Skipped;
1480                }
1481                if drop_schema.cascade {
1482                    self.snapshot_namespace();
1483                    let mut relations_to_drop = Vec::new();
1484                    for id in self.local.relations.keys() {
1485                        if drop_schema.names.contains(&id.schema) {
1486                            relations_to_drop.push(id.clone());
1487                        }
1488                    }
1489                    for id in relations_to_drop {
1490                        self.snapshot_relation(&id);
1491                        self.local.relations.insert(id, RelationOverlay::Dropped);
1492                    }
1493
1494                    let constraints_to_drop: Vec<(ObjectId, String)> = self
1495                        .local
1496                        .constraints
1497                        .keys()
1498                        .filter(|(table_id, _)| drop_schema.names.contains(&table_id.schema))
1499                        .cloned()
1500                        .collect();
1501                    for (table_id, name) in constraints_to_drop {
1502                        self.snapshot_constraint(&table_id, &name);
1503                        self.local.constraints.remove(&(table_id, name));
1504                    }
1505
1506                    let mut types_to_drop = Vec::new();
1507                    for id in self.local.types.keys() {
1508                        if drop_schema.names.contains(&id.schema) {
1509                            types_to_drop.push(id.clone());
1510                        }
1511                    }
1512                    for id in types_to_drop {
1513                        self.snapshot_type(&id);
1514                        self.local.types.insert(id, TypeOverlay::Dropped);
1515                    }
1516
1517                    let mut seqs_to_drop = Vec::new();
1518                    for id in self.local.sequences.keys() {
1519                        if drop_schema.names.contains(&id.schema) {
1520                            seqs_to_drop.push(id.clone());
1521                        }
1522                    }
1523                    for id in seqs_to_drop {
1524                        self.snapshot_sequence(&id);
1525                        self.local.sequences.insert(id, SequenceOverlay::Dropped);
1526                    }
1527
1528                    let functions_to_drop: Vec<ObjectId> = self
1529                        .local
1530                        .functions
1531                        .keys()
1532                        .filter(|id| drop_schema.names.contains(&id.schema))
1533                        .cloned()
1534                        .collect();
1535                    for id in functions_to_drop {
1536                        self.snapshot_function(&id);
1537                        self.local
1538                            .functions
1539                            .insert(id, crate::model::function::FunctionOverlay::Dropped);
1540                    }
1541
1542                    let triggers_to_drop: Vec<ObjectId> = self
1543                        .local
1544                        .triggers
1545                        .keys()
1546                        .filter(|id| drop_schema.names.contains(&id.schema))
1547                        .cloned()
1548                        .collect();
1549                    for id in triggers_to_drop {
1550                        self.snapshot_trigger(&id);
1551                        self.local.triggers.insert(id, TriggerOverlay::Dropped);
1552                    }
1553
1554                    self.local
1555                        .pending_validation
1556                        .retain(|(table, _)| !drop_schema.names.contains(&table.schema));
1557                    for overlay in self.local.publications.values_mut() {
1558                        let crate::model::replication::PublicationOverlay::Present(publication) =
1559                            overlay
1560                        else {
1561                            continue;
1562                        };
1563                        let crate::analysis::facts::PublicationScope::Explicit(objects) =
1564                            &mut publication.scope
1565                        else {
1566                            continue;
1567                        };
1568                        objects.retain(|object| match object {
1569                            crate::analysis::facts::PublicationObjectFact::Table {
1570                                name, ..
1571                            } => name.schema.as_ref().is_none_or(|schema| {
1572                                !drop_schema.names.contains(&schema.resolve())
1573                            }),
1574                            crate::analysis::facts::PublicationObjectFact::SchemaTables {
1575                                schema,
1576                                ..
1577                            } => !drop_schema.names.contains(schema),
1578                            _ => true,
1579                        });
1580                    }
1581
1582                    self.snapshot_graph_full();
1583
1584                    let g = &mut self.local.graph;
1585                    g.edges.retain(|e| {
1586                        !drop_schema.names.contains(&e.dependent.schema)
1587                            && !drop_schema.names.contains(&e.referenced.schema)
1588                            && match &e.kind {
1589                                DependencyKind::TriggerOnTable { function_id, .. } => {
1590                                    !drop_schema.names.contains(&function_id.schema)
1591                                }
1592                                _ => true,
1593                            }
1594                    });
1595                } else {
1596                    // Non-cascade: fail if any objects in the schema still exist
1597                    let has_relation = self.local.relations.iter().any(|(id, ov)| {
1598                        drop_schema.names.contains(&id.schema)
1599                            && !matches!(ov, RelationOverlay::Dropped)
1600                    });
1601                    let has_type = self.local.types.iter().any(|(id, ov)| {
1602                        drop_schema.names.contains(&id.schema)
1603                            && !matches!(ov, TypeOverlay::Dropped)
1604                    });
1605                    let has_sequence = self.local.sequences.iter().any(|(id, ov)| {
1606                        drop_schema.names.contains(&id.schema)
1607                            && !matches!(ov, SequenceOverlay::Dropped)
1608                    });
1609                    let has_function = self.local.functions.iter().any(|(id, ov)| {
1610                        drop_schema.names.contains(&id.schema)
1611                            && !matches!(ov, crate::model::function::FunctionOverlay::Dropped)
1612                    });
1613                    let has_trigger = self.local.triggers.iter().any(|(id, ov)| {
1614                        drop_schema.names.contains(&id.schema)
1615                            && !matches!(ov, TriggerOverlay::Dropped)
1616                    });
1617                    if has_relation || has_type || has_sequence || has_function || has_trigger {
1618                        return MutationResult::Conflict {
1619                            reason: format!(
1620                                "schema(s) {:?} still contain objects; use CASCADE to drop them",
1621                                drop_schema.names
1622                            ),
1623                        };
1624                    }
1625                }
1626                for name in present_names {
1627                    self.snapshot_schema(&name);
1628                    self.local.schemas.insert(name, SchemaOverlay::Dropped);
1629                }
1630                self.snapshot_search_path();
1631                self.refresh_role_sensitive_search_path();
1632                MutationResult::Applied
1633            }
1634            Mutation::DropTable(drop_table) => {
1635                if !self.relation_is_present(&drop_table.id) {
1636                    if drop_table.if_exists {
1637                        return MutationResult::Skipped;
1638                    } else {
1639                        self.local.confidence = Confidence::Tainted;
1640                        return MutationResult::Skipped;
1641                    }
1642                }
1643
1644                let renames: Vec<DependencyEdge> = self
1645                    .local
1646                    .graph
1647                    .edges
1648                    .iter()
1649                    .filter(|e| matches!(e.kind, DependencyKind::RenameTo))
1650                    .cloned()
1651                    .collect();
1652                let resolve = |id: &ObjectId| -> ObjectId {
1653                    let mut current = id;
1654                    let mut visited = HashSet::new();
1655                    loop {
1656                        if !visited.insert(current.clone()) {
1657                            return id.clone();
1658                        }
1659                        match renames.iter().find(|r| &r.dependent == current) {
1660                            Some(edge) => current = &edge.referenced,
1661                            None => return current.clone(),
1662                        }
1663                    }
1664                };
1665
1666                let resolved_drop = resolve(&drop_table.id);
1667                let mut dropped_relations = HashSet::from([resolved_drop.clone()]);
1668
1669                if drop_table.cascade {
1670                    let local_closure;
1671                    let closure = match precomputed_cascade {
1672                        Some(c) => c,
1673                        None => {
1674                            local_closure = self.get_cascade_closure(&drop_table.id);
1675                            &local_closure
1676                        }
1677                    };
1678                    dropped_relations = closure.dropped_relations.clone();
1679
1680                    for dropped_rel_id in &closure.dropped_relations {
1681                        self.snapshot_relation(dropped_rel_id);
1682                        self.local
1683                            .relations
1684                            .insert(dropped_rel_id.clone(), RelationOverlay::Dropped);
1685                    }
1686
1687                    self.snapshot_graph_full();
1688                    self.local.graph.edges.retain(|e| match &e.kind {
1689                        DependencyKind::IndexOnRelation { .. } => {
1690                            !closure.dropped_indexes.contains(&resolve(&e.dependent))
1691                        }
1692                        DependencyKind::ForeignKey {
1693                            constraint_name, ..
1694                        } => {
1695                            let from_dropped =
1696                                closure.dropped_relations.contains(&resolve(&e.dependent));
1697                            let to_dropped =
1698                                closure.dropped_relations.contains(&resolve(&e.referenced));
1699                            let constraint_explicitly_dropped = if let Some(cname) = constraint_name
1700                            {
1701                                closure
1702                                    .dropped_constraints
1703                                    .contains(&(resolve(&e.dependent), cname.clone()))
1704                            } else {
1705                                false
1706                            };
1707                            !(from_dropped || to_dropped || constraint_explicitly_dropped)
1708                        }
1709                        DependencyKind::ViewDependency { .. } => {
1710                            !closure.dropped_relations.contains(&resolve(&e.dependent))
1711                        }
1712                        DependencyKind::SequenceOwnedBy { .. } => {
1713                            !closure.dropped_relations.contains(&resolve(&e.referenced))
1714                        }
1715                        _ => true,
1716                    });
1717                } else {
1718                    let has_view_deps = self.local.graph.edges.iter().any(|e| {
1719                        matches!(e.kind, DependencyKind::ViewDependency { .. })
1720                            && resolve(&e.referenced) == resolved_drop
1721                    });
1722                    let has_fk_deps = self.local.graph.edges.iter().any(|e| {
1723                        matches!(e.kind, DependencyKind::ForeignKey { .. })
1724                            && resolve(&e.referenced) == resolved_drop
1725                            && resolve(&e.dependent) != resolved_drop
1726                    });
1727                    let has_partition_deps = self.local.graph.edges.iter().any(|e| {
1728                        matches!(e.kind, DependencyKind::PartitionOf)
1729                            && resolve(&e.referenced) == resolved_drop
1730                    });
1731
1732                    if has_view_deps || has_fk_deps || has_partition_deps {
1733                        return MutationResult::Conflict {
1734                            reason: format!(
1735                                "relation '{}' still has dependent objects; use CASCADE",
1736                                drop_table.id
1737                            ),
1738                        };
1739                    }
1740
1741                    self.snapshot_relation(&drop_table.id);
1742                    self.local
1743                        .relations
1744                        .insert(drop_table.id.clone(), RelationOverlay::Dropped);
1745
1746                    self.snapshot_graph_full();
1747                    self.local.graph.edges.retain(|e| {
1748                        !(matches!(e.kind, DependencyKind::SequenceOwnedBy { .. })
1749                            && resolve(&e.referenced) == resolved_drop)
1750                    });
1751                }
1752
1753                let owned_sequences_to_drop: Vec<ObjectId> = self
1754                    .local
1755                    .sequences
1756                    .iter()
1757                    .filter_map(|(id, overlay)| match overlay {
1758                        SequenceOverlay::Present(sequence)
1759                            if sequence.owned_by.as_ref().is_some_and(|(table, _)| {
1760                                dropped_relations.contains(&resolve(table))
1761                            }) =>
1762                        {
1763                            Some(id.clone())
1764                        }
1765                        _ => None,
1766                    })
1767                    .collect();
1768                for sequence_id in owned_sequences_to_drop {
1769                    self.snapshot_sequence(&sequence_id);
1770                    self.local
1771                        .sequences
1772                        .insert(sequence_id, SequenceOverlay::Dropped);
1773                }
1774
1775                let constraints_to_drop: Vec<(ObjectId, String)> = self
1776                    .local
1777                    .constraints
1778                    .keys()
1779                    .filter(|(table_id, _)| dropped_relations.contains(&resolve(table_id)))
1780                    .cloned()
1781                    .collect();
1782                for (table_id, name) in constraints_to_drop {
1783                    self.snapshot_constraint(&table_id, &name);
1784                    self.local.constraints.remove(&(table_id, name));
1785                }
1786
1787                let triggers_to_drop: Vec<ObjectId> = self
1788                    .local
1789                    .triggers
1790                    .iter()
1791                    .filter_map(|(id, overlay)| {
1792                        let TriggerOverlay::Present(trigger) = overlay else {
1793                            return None;
1794                        };
1795                        let graph_matches = self.local.graph.edges.iter().any(|edge| {
1796                            matches!(edge.kind, DependencyKind::TriggerOnTable { .. })
1797                                && edge.dependent == *id
1798                                && dropped_relations.contains(&resolve(&edge.referenced))
1799                        });
1800                        (dropped_relations.contains(&resolve(&trigger.table_id)) || graph_matches)
1801                            .then(|| id.clone())
1802                    })
1803                    .collect();
1804                for trigger_id in triggers_to_drop {
1805                    self.snapshot_trigger(&trigger_id);
1806                    self.local
1807                        .triggers
1808                        .insert(trigger_id, TriggerOverlay::Dropped);
1809                }
1810
1811                // PostgreSQL drops triggers only after the table drop succeeds.
1812                self.snapshot_graph_full();
1813                self.local.graph.edges.retain(|e| {
1814                    !(matches!(e.kind, DependencyKind::TriggerOnTable { .. })
1815                        && dropped_relations.contains(&resolve(&e.referenced)))
1816                });
1817
1818                self.snapshot_graph_full();
1819                self.local.graph.edges.retain(|e| {
1820                    if let DependencyKind::PartitionOf = e.kind {
1821                        resolve(&e.referenced) != resolved_drop
1822                            && resolve(&e.dependent) != resolved_drop
1823                    } else {
1824                        true
1825                    }
1826                });
1827
1828                MutationResult::Applied
1829            }
1830            Mutation::CreateTable(create) => {
1831                if create.if_not_exists && self.relation_namespace_is_taken(&create.id) {
1832                    return MutationResult::Skipped;
1833                }
1834                if self.relation_namespace_is_taken(&create.id) {
1835                    return MutationResult::Conflict {
1836                        reason: format!("relation '{}' already exists", create.id),
1837                    };
1838                }
1839
1840                // PostgreSQL chooses all implicit sequence names before the
1841                // table becomes visible. Reserve them up front so a collision
1842                // or malformed statement cannot leave partial local state.
1843                let mut reserved_sequences = HashSet::new();
1844                let mut implicit_sequences = Vec::new();
1845                for column in &create.columns {
1846                    let kind = match column.generation {
1847                        crate::analysis::facts::ColumnGeneration::Serial => {
1848                            Some(SequenceKind::SerialLike)
1849                        }
1850                        crate::analysis::facts::ColumnGeneration::Identity => {
1851                            Some(SequenceKind::Identity)
1852                        }
1853                        crate::analysis::facts::ColumnGeneration::Ordinary => None,
1854                    };
1855                    if let Some(kind) = kind {
1856                        let sequence_id = self.next_implicit_sequence_id(
1857                            &create.id,
1858                            &column.name,
1859                            &reserved_sequences,
1860                        );
1861                        reserved_sequences.insert(sequence_id.clone());
1862                        implicit_sequences.push((sequence_id, column.name.clone(), kind));
1863                    }
1864                }
1865
1866                self.snapshot_relation(&create.id);
1867
1868                self.snapshot_generation_counter();
1869                self.local.generation_counter += 1;
1870                let generation = self.local.generation_counter;
1871
1872                let resolved_persistence = match create.persistence {
1873                    PersistenceMutation::Permanent => {
1874                        crate::model::relation::Persistence::Permanent
1875                    }
1876                    PersistenceMutation::Temporary => {
1877                        crate::model::relation::Persistence::Temporary
1878                    }
1879                    PersistenceMutation::Unlogged => crate::model::relation::Persistence::Unlogged,
1880                };
1881
1882                let mut rel_state = RelationState::new(
1883                    create.id.clone(),
1884                    ObjectId::new("", &self.local.current_role),
1885                    generation,
1886                    if create.as_select { None } else { Some(0) },
1887                    RelationKind::Table,
1888                    resolved_persistence,
1889                    self.local.transactions.len(),
1890                );
1891
1892                // Store partition strategy information
1893                rel_state.partition_type = create
1894                    .partition_by
1895                    .as_ref()
1896                    .and_then(|pb| pb.split_whitespace().nth(2).map(|s| s.to_uppercase()))
1897                    .or_else(|| {
1898                        create.partition_of.as_ref().and_then(|parent_id| {
1899                            self.local.relations.get(parent_id).and_then(|r| {
1900                                if let RelationOverlay::Present(rel) = r {
1901                                    rel.partition_type.clone()
1902                                } else {
1903                                    None
1904                                }
1905                            })
1906                        })
1907                    });
1908                rel_state.partition_by = create.partition_by.clone();
1909
1910                let pk_columns: HashSet<&str> = create
1911                    .table_constraints
1912                    .iter()
1913                    .filter_map(|tc| {
1914                        if let TableConstraintFact::PrimaryKey { columns, .. } = tc {
1915                            Some(columns.iter().map(|s| s.as_str()))
1916                        } else {
1917                            None
1918                        }
1919                    })
1920                    .flatten()
1921                    .collect();
1922
1923                for col in &create.columns {
1924                    let is_pk = col.is_primary_key || pk_columns.contains(col.name.as_str());
1925                    rel_state.apply_column_action(&ColumnAction::Add {
1926                        name: col.name.clone(),
1927                        data_type: col.ty.clone(),
1928                        not_null: col.not_null || is_pk,
1929                        default: col.default.clone(),
1930                    });
1931                    if let Some(column) = rel_state
1932                        .columns
1933                        .iter_mut()
1934                        .find(|column| column.name == col.name)
1935                    {
1936                        column.type_id = column
1937                            .data_type
1938                            .as_deref()
1939                            .and_then(|raw| self.resolve_type_reference(raw));
1940                    }
1941                }
1942
1943                for (sequence_id, column_name, _) in &implicit_sequences {
1944                    if let Some(column) = rel_state
1945                        .columns
1946                        .iter_mut()
1947                        .find(|column| column.name == *column_name)
1948                    {
1949                        column.default = Some(Self::sequence_nextval_default(sequence_id));
1950                        column.default_expr_text = Some(format!(
1951                            "nextval('{}.{}'::regclass)",
1952                            sequence_id.schema, sequence_id.name
1953                        ));
1954                        column.is_nullable = false;
1955                    }
1956                }
1957
1958                self.local
1959                    .relations
1960                    .insert(create.id.clone(), RelationOverlay::Present(rel_state));
1961
1962                for (sequence_id, column_name, kind) in implicit_sequences {
1963                    self.snapshot_sequence(&sequence_id);
1964                    self.snapshot_generation_counter();
1965                    self.local.generation_counter += 1;
1966                    self.local.sequences.insert(
1967                        sequence_id.clone(),
1968                        SequenceOverlay::Present(SequenceState {
1969                            id: sequence_id.clone(),
1970                            owner: ObjectId::new("", &self.local.current_role),
1971                            owned_by: Some((create.id.clone(), column_name.clone())),
1972                            kind,
1973                            generation: self.local.generation_counter,
1974                        }),
1975                    );
1976                    self.snapshot_graph();
1977                    self.local.graph.edges.push(DependencyEdge::new(
1978                        sequence_id,
1979                        create.id.clone(),
1980                        DependencyKind::SequenceOwnedBy {
1981                            column: column_name,
1982                        },
1983                    ));
1984                }
1985
1986                let primary_key_name = create
1987                    .columns
1988                    .iter()
1989                    .find(|column| column.is_primary_key)
1990                    .map(|column| column.primary_key_constraint_name.clone())
1991                    .or_else(|| {
1992                        create.table_constraints.iter().find_map(|constraint| {
1993                            if let TableConstraintFact::PrimaryKey {
1994                                constraint_name, ..
1995                            } = constraint
1996                            {
1997                                Some(constraint_name.clone())
1998                            } else {
1999                                None
2000                            }
2001                        })
2002                    });
2003                if let Some(explicit_name) = primary_key_name {
2004                    let name = explicit_name.unwrap_or_else(|| {
2005                        self.next_generated_constraint_name(
2006                            &create.id,
2007                            &create.id.name,
2008                            None,
2009                            "pkey",
2010                        )
2011                    });
2012                    self.snapshot_constraint(&create.id, &name);
2013                    self.local.constraints.insert(
2014                        (create.id.clone(), name.clone()),
2015                        ConstraintState {
2016                            table_id: create.id.clone(),
2017                            name,
2018                            kind: ConstraintKind::PrimaryKey,
2019                            validated: true,
2020                        },
2021                    );
2022                }
2023
2024                let unique_constraints = create
2025                    .columns
2026                    .iter()
2027                    .filter(|column| column.is_unique)
2028                    .map(|column| {
2029                        (
2030                            column.unique_constraint_name.as_ref(),
2031                            vec![column.name.as_str()],
2032                        )
2033                    })
2034                    .chain(create.table_constraints.iter().filter_map(|constraint| {
2035                        if let TableConstraintFact::Unique {
2036                            constraint_name,
2037                            columns,
2038                        } = constraint
2039                        {
2040                            Some((
2041                                constraint_name.as_ref(),
2042                                columns.iter().map(String::as_str).collect(),
2043                            ))
2044                        } else {
2045                            None
2046                        }
2047                    }))
2048                    .collect::<Vec<_>>();
2049                for (explicit_name, columns) in unique_constraints {
2050                    let name = explicit_name.cloned().unwrap_or_else(|| {
2051                        self.next_generated_constraint_name(
2052                            &create.id,
2053                            &create.id.name,
2054                            Some(&columns.join("_")),
2055                            "key",
2056                        )
2057                    });
2058                    self.snapshot_constraint(&create.id, &name);
2059                    self.local.constraints.insert(
2060                        (create.id.clone(), name.clone()),
2061                        ConstraintState {
2062                            table_id: create.id.clone(),
2063                            name,
2064                            kind: ConstraintKind::Unique,
2065                            validated: true,
2066                        },
2067                    );
2068                }
2069
2070                if let Some(parent_id) = &create.partition_of {
2071                    self.snapshot_graph();
2072                    self.local.graph.edges.push(DependencyEdge::new(
2073                        create.id.clone(),
2074                        parent_id.clone(),
2075                        DependencyKind::PartitionOf,
2076                    ));
2077                }
2078
2079                if !create.foreign_keys.is_empty() {
2080                    self.snapshot_graph();
2081                }
2082
2083                for fk in &create.foreign_keys {
2084                    self.local.graph.edges.push(DependencyEdge::new(
2085                        create.id.clone(),
2086                        fk.to_table.clone(),
2087                        DependencyKind::ForeignKey {
2088                            constraint_name: fk.constraint_name.clone(),
2089                            from_columns: fk.from_columns.clone(),
2090                            to_columns: fk.to_columns.clone(),
2091                            from_generation: generation,
2092                        },
2093                    ));
2094                }
2095                MutationResult::Applied
2096            }
2097            Mutation::CreateView(create_view) => {
2098                if self.relation_namespace_is_taken(&create_view.id) {
2099                    let is_replaceable_view = matches!(
2100                        self.local.relations.get(&create_view.id),
2101                        Some(RelationOverlay::Present(relation))
2102                            if relation.kind == RelationKind::View
2103                    );
2104                    if !create_view.or_replace || !is_replaceable_view {
2105                        return MutationResult::Conflict {
2106                            reason: format!("relation '{}' already exists", create_view.id),
2107                        };
2108                    }
2109                }
2110                self.snapshot_relation(&create_view.id);
2111                self.snapshot_generation_counter();
2112                self.local.generation_counter += 1;
2113                let generation = self.local.generation_counter;
2114
2115                self.local.relations.insert(
2116                    create_view.id.clone(),
2117                    RelationOverlay::Present(RelationState::new(
2118                        create_view.id.clone(),
2119                        ObjectId::new("", &self.local.current_role),
2120                        generation,
2121                        None,
2122                        RelationKind::View,
2123                        Persistence::Permanent,
2124                        self.local.transactions.len(),
2125                    )),
2126                );
2127
2128                self.snapshot_graph();
2129                for dep in &create_view.depends_on {
2130                    self.local.graph.edges.push(DependencyEdge::new(
2131                        create_view.id.clone(),
2132                        dep.clone(),
2133                        DependencyKind::ViewDependency {
2134                            view_generation: generation,
2135                        },
2136                    ));
2137                }
2138                MutationResult::Applied
2139            }
2140            Mutation::CreateMaterializedView(create_mv) => {
2141                if self.relation_namespace_is_taken(&create_mv.id) {
2142                    return MutationResult::Conflict {
2143                        reason: format!("relation '{}' already exists", create_mv.id),
2144                    };
2145                }
2146                self.snapshot_relation(&create_mv.id);
2147                self.snapshot_generation_counter();
2148                self.local.generation_counter += 1;
2149                let generation = self.local.generation_counter;
2150
2151                self.local.relations.insert(
2152                    create_mv.id.clone(),
2153                    RelationOverlay::Present(RelationState::new(
2154                        create_mv.id.clone(),
2155                        ObjectId::new("", &self.local.current_role),
2156                        generation,
2157                        None,
2158                        RelationKind::MaterializedView,
2159                        Persistence::Permanent,
2160                        self.local.transactions.len(),
2161                    )),
2162                );
2163
2164                self.snapshot_graph();
2165                for dep in &create_mv.depends_on {
2166                    self.local.graph.edges.push(DependencyEdge::new(
2167                        create_mv.id.clone(),
2168                        dep.clone(),
2169                        DependencyKind::ViewDependency {
2170                            view_generation: generation,
2171                        },
2172                    ));
2173                }
2174                MutationResult::Applied
2175            }
2176            Mutation::RefreshMaterializedView(_) => MutationResult::Applied,
2177            Mutation::CreateIndex(create_idx) => {
2178                let exists = self.index_is_present(&create_idx.id);
2179                if create_idx.if_not_exists && exists {
2180                    return MutationResult::Skipped;
2181                }
2182                if self.relation_namespace_is_taken(&create_idx.id) {
2183                    return MutationResult::Conflict {
2184                        reason: format!("relation '{}' already exists", create_idx.id),
2185                    };
2186                }
2187                self.snapshot_graph();
2188                self.local.graph.edges.push(DependencyEdge::new(
2189                    create_idx.id.clone(),
2190                    create_idx.table.clone(),
2191                    DependencyKind::IndexOnRelation {
2192                        using_method: create_idx.using_method.clone(),
2193                        has_predicate: create_idx.has_predicate,
2194                        is_concurrent: create_idx.concurrently,
2195                        is_unique: create_idx.unique,
2196                        eligibility_known: true,
2197                    },
2198                ));
2199                MutationResult::Applied
2200            }
2201            Mutation::CreatePolicy(create_policy) => {
2202                self.snapshot_relation(&create_policy.table);
2203                if let Some(RelationOverlay::Present(rel)) =
2204                    self.local.relations.get_mut(&create_policy.table)
2205                {
2206                    if rel.policies.contains(&create_policy.name) {
2207                        return MutationResult::Conflict {
2208                            reason: format!(
2209                                "policy '{}' already exists on relation '{}'",
2210                                create_policy.name, create_policy.table
2211                            ),
2212                        };
2213                    }
2214                    rel.policies.insert(create_policy.name.clone());
2215                } else {
2216                    return MutationResult::Conflict {
2217                        reason: format!("relation '{}' does not exist", create_policy.table),
2218                    };
2219                }
2220                MutationResult::Applied
2221            }
2222            Mutation::DropPolicy(drop_policy) => {
2223                self.snapshot_relation(&drop_policy.table);
2224                if let Some(RelationOverlay::Present(rel)) =
2225                    self.local.relations.get_mut(&drop_policy.table)
2226                {
2227                    if !rel.policies.contains(&drop_policy.name) {
2228                        return if drop_policy.if_exists {
2229                            MutationResult::Skipped
2230                        } else {
2231                            MutationResult::Conflict {
2232                                reason: format!(
2233                                    "policy '{}' does not exist on relation '{}'",
2234                                    drop_policy.name, drop_policy.table
2235                                ),
2236                            }
2237                        };
2238                    }
2239                    rel.policies.remove(&drop_policy.name);
2240                } else {
2241                    return MutationResult::Conflict {
2242                        reason: format!("relation '{}' does not exist", drop_policy.table),
2243                    };
2244                }
2245                MutationResult::Applied
2246            }
2247            Mutation::CreateTrigger(create_trigger) => {
2248                let trigger_id = Self::trigger_key(&create_trigger.table, &create_trigger.name);
2249                if matches!(
2250                    self.local.triggers.get(&trigger_id),
2251                    Some(TriggerOverlay::Present(_))
2252                ) {
2253                    return MutationResult::Conflict {
2254                        reason: format!(
2255                            "trigger '{}' already exists on relation '{}'",
2256                            create_trigger.name, create_trigger.table
2257                        ),
2258                    };
2259                }
2260                self.snapshot_trigger(&trigger_id);
2261                self.local.triggers.insert(
2262                    trigger_id.clone(),
2263                    TriggerOverlay::Present(crate::model::trigger::TriggerState {
2264                        name: create_trigger.name.clone(),
2265                        id: trigger_id.clone(),
2266                        table_id: create_trigger.table.clone(),
2267                        enabled_mode: crate::model::trigger::TriggerEnableMode::Origin,
2268                        generation: self.local.generation_counter,
2269                    }),
2270                );
2271
2272                self.snapshot_relation(&create_trigger.table);
2273                if let Some(RelationOverlay::Present(rel)) =
2274                    self.local.relations.get_mut(&create_trigger.table)
2275                {
2276                    rel.triggers.insert(create_trigger.name.clone());
2277                }
2278
2279                self.snapshot_graph_full();
2280                self.local.graph.edges.push(DependencyEdge::new(
2281                    trigger_id.clone(),
2282                    create_trigger.table.clone(),
2283                    DependencyKind::TriggerOnTable {
2284                        trigger_id: trigger_id.clone(),
2285                        function_id: create_trigger.function_id.clone(),
2286                    },
2287                ));
2288
2289                MutationResult::Applied
2290            }
2291            Mutation::DropTrigger(drop_trigger) => {
2292                let trigger_id = Self::trigger_key(&drop_trigger.table, &drop_trigger.name);
2293                if !matches!(
2294                    self.local.triggers.get(&trigger_id),
2295                    Some(TriggerOverlay::Present(_))
2296                ) {
2297                    return if drop_trigger.if_exists {
2298                        MutationResult::Skipped
2299                    } else {
2300                        MutationResult::Conflict {
2301                            reason: format!(
2302                                "trigger '{}' does not exist on relation '{}'",
2303                                drop_trigger.name, drop_trigger.table
2304                            ),
2305                        }
2306                    };
2307                }
2308                self.snapshot_trigger(&trigger_id);
2309                self.local
2310                    .triggers
2311                    .insert(trigger_id.clone(), TriggerOverlay::Dropped);
2312
2313                self.snapshot_relation(&drop_trigger.table);
2314                if let Some(RelationOverlay::Present(rel)) =
2315                    self.local.relations.get_mut(&drop_trigger.table)
2316                {
2317                    rel.triggers.remove(&drop_trigger.name);
2318                }
2319
2320                self.snapshot_graph_full();
2321                self.local.graph.edges.retain(|e| {
2322                    !(matches!(e.kind, DependencyKind::TriggerOnTable { .. })
2323                        && e.dependent == trigger_id)
2324                });
2325
2326                MutationResult::Applied
2327            }
2328            Mutation::RenameTrigger(rename_trigger) => {
2329                let old_id = Self::trigger_key(&rename_trigger.table, &rename_trigger.name);
2330                let new_id = Self::trigger_key(&rename_trigger.table, &rename_trigger.new_name);
2331                let Some(TriggerOverlay::Present(mut trigger)) =
2332                    self.local.triggers.get(&old_id).cloned()
2333                else {
2334                    return MutationResult::Conflict {
2335                        reason: format!(
2336                            "trigger '{}' does not exist on relation '{}'",
2337                            rename_trigger.name, rename_trigger.table
2338                        ),
2339                    };
2340                };
2341                if old_id != new_id
2342                    && matches!(
2343                        self.local.triggers.get(&new_id),
2344                        Some(TriggerOverlay::Present(_))
2345                    )
2346                {
2347                    return MutationResult::Conflict {
2348                        reason: format!(
2349                            "trigger '{}' already exists on relation '{}'",
2350                            rename_trigger.new_name, rename_trigger.table
2351                        ),
2352                    };
2353                }
2354                self.snapshot_trigger(&old_id);
2355                self.snapshot_trigger(&new_id);
2356                self.snapshot_relation(&rename_trigger.table);
2357                self.snapshot_graph_full();
2358                self.local.triggers.remove(&old_id);
2359                trigger.id = new_id.clone();
2360                trigger.name = rename_trigger.new_name.clone();
2361                self.local
2362                    .triggers
2363                    .insert(new_id.clone(), TriggerOverlay::Present(trigger));
2364                if let Some(RelationOverlay::Present(relation)) =
2365                    self.local.relations.get_mut(&rename_trigger.table)
2366                {
2367                    relation.triggers.remove(&rename_trigger.name);
2368                    relation.triggers.insert(rename_trigger.new_name.clone());
2369                }
2370                self.local.graph.propagate_rename(&old_id, &new_id);
2371                self.local.graph.edges.push(DependencyEdge::new(
2372                    old_id,
2373                    new_id,
2374                    DependencyKind::RenameTo,
2375                ));
2376                MutationResult::Applied
2377            }
2378            Mutation::AlterTable(alter) => {
2379                if let AlterTableActionMutation::OwnerTo { new_owner } = &alter.action {
2380                    let Some((owner, known)) = self.role_fact_identity(new_owner) else {
2381                        self.snapshot_confidence();
2382                        self.local.confidence = Confidence::Tainted;
2383                        return MutationResult::Skipped;
2384                    };
2385                    if !known {
2386                        self.snapshot_confidence();
2387                        self.local.confidence = Confidence::Tainted;
2388                    }
2389                    self.snapshot_relation(&alter.id);
2390                    return match self.local.relations.get_mut(&alter.id) {
2391                        Some(RelationOverlay::Present(relation)) => {
2392                            relation.owner = ObjectId::new("", owner);
2393                            MutationResult::Applied
2394                        }
2395                        _ => MutationResult::Conflict {
2396                            reason: format!("relation '{}' does not exist", alter.id),
2397                        },
2398                    };
2399                }
2400
2401                let trigger_mode = match &alter.action {
2402                    AlterTableActionMutation::DisableTrigger { trigger_name } => Some((
2403                        trigger_name.as_deref(),
2404                        crate::model::trigger::TriggerEnableMode::Disabled,
2405                    )),
2406                    AlterTableActionMutation::EnableTrigger { trigger_name } => Some((
2407                        trigger_name.as_deref(),
2408                        crate::model::trigger::TriggerEnableMode::Origin,
2409                    )),
2410                    _ => None,
2411                };
2412                if let Some((trigger_name, enabled_mode)) = trigger_mode {
2413                    let all = trigger_name.is_none_or(|name| name.eq_ignore_ascii_case("all"));
2414                    let trigger_ids: Vec<ObjectId> = self
2415                        .local
2416                        .triggers
2417                        .iter()
2418                        .filter_map(|(id, overlay)| {
2419                            let TriggerOverlay::Present(trigger) = overlay else {
2420                                return None;
2421                            };
2422                            (trigger.table_id == alter.id
2423                                && (all || trigger_name == Some(trigger.name.as_str())))
2424                            .then(|| id.clone())
2425                        })
2426                        .collect();
2427                    for trigger_id in trigger_ids {
2428                        self.snapshot_trigger(&trigger_id);
2429                        if let Some(TriggerOverlay::Present(trigger)) =
2430                            self.local.triggers.get_mut(&trigger_id)
2431                        {
2432                            trigger.enabled_mode = enabled_mode;
2433                        }
2434                    }
2435                    return MutationResult::Applied;
2436                }
2437
2438                if let AlterTableActionMutation::AddForeignKey {
2439                    to_table,
2440                    from_columns,
2441                    to_columns,
2442                    ..
2443                } = &alter.action
2444                {
2445                    if let Some(RelationOverlay::Present(child)) =
2446                        self.local.relations.get(&alter.id)
2447                        && let Some(column) =
2448                            from_columns.iter().find(|column| !child.has_column(column))
2449                    {
2450                        return MutationResult::Conflict {
2451                            reason: format!(
2452                                "foreign key column '{}' does not exist on relation '{}'",
2453                                column, alter.id
2454                            ),
2455                        };
2456                    }
2457
2458                    let Some(RelationOverlay::Present(parent)) = self.local.relations.get(to_table)
2459                    else {
2460                        return MutationResult::Conflict {
2461                            reason: format!(
2462                                "foreign key references relation '{}' which does not exist",
2463                                to_table
2464                            ),
2465                        };
2466                    };
2467                    if let Some(column) =
2468                        to_columns.iter().find(|column| !parent.has_column(column))
2469                    {
2470                        return MutationResult::Conflict {
2471                            reason: format!(
2472                                "foreign key references column '{}.{}' which does not exist",
2473                                to_table, column
2474                            ),
2475                        };
2476                    }
2477                }
2478
2479                let implicit_add = match &alter.action {
2480                    AlterTableActionMutation::AddColumn {
2481                        name, generation, ..
2482                    } => match generation {
2483                        crate::analysis::facts::ColumnGeneration::Serial => Some((
2484                            self.next_implicit_sequence_id(&alter.id, name, &HashSet::new()),
2485                            name.clone(),
2486                            SequenceKind::SerialLike,
2487                        )),
2488                        crate::analysis::facts::ColumnGeneration::Identity => Some((
2489                            self.next_implicit_sequence_id(&alter.id, name, &HashSet::new()),
2490                            name.clone(),
2491                            SequenceKind::Identity,
2492                        )),
2493                        crate::analysis::facts::ColumnGeneration::Ordinary => None,
2494                    },
2495                    _ => None,
2496                };
2497                let owned_sequences_for_column: Vec<ObjectId> = match &alter.action {
2498                    AlterTableActionMutation::DropColumn { name, .. }
2499                    | AlterTableActionMutation::RenameColumn { from: name, .. } => self
2500                        .local
2501                        .sequences
2502                        .iter()
2503                        .filter_map(|(id, overlay)| match overlay {
2504                            SequenceOverlay::Present(sequence)
2505                                if sequence.owned_by.as_ref()
2506                                    == Some(&(alter.id.clone(), name.clone())) =>
2507                            {
2508                                Some(id.clone())
2509                            }
2510                            _ => None,
2511                        })
2512                        .collect(),
2513                    _ => Vec::new(),
2514                };
2515
2516                let using_index = match &alter.action {
2517                    AlterTableActionMutation::AddUniqueConstraint { using_index, .. }
2518                    | AlterTableActionMutation::AddPrimaryKeyConstraint { using_index, .. } => {
2519                        using_index.as_ref()
2520                    }
2521                    _ => None,
2522                };
2523                if let Some(index) = using_index {
2524                    let Some(edge) = self.local.graph.edges.iter().find(|edge| {
2525                        matches!(edge.kind, DependencyKind::IndexOnRelation { .. })
2526                            && edge.dependent == *index
2527                    }) else {
2528                        return MutationResult::Conflict {
2529                            reason: format!(
2530                                "constraint references index '{}' which does not exist",
2531                                index
2532                            ),
2533                        };
2534                    };
2535                    if edge.referenced != alter.id {
2536                        return MutationResult::Conflict {
2537                            reason: format!(
2538                                "constraint index '{}' belongs to relation '{}', not '{}'",
2539                                index, edge.referenced, alter.id
2540                            ),
2541                        };
2542                    }
2543                    if let DependencyKind::IndexOnRelation {
2544                        has_predicate,
2545                        is_unique,
2546                        eligibility_known,
2547                        ..
2548                    } = &edge.kind
2549                        && *eligibility_known
2550                        && (!is_unique || *has_predicate)
2551                    {
2552                        return MutationResult::Conflict {
2553                            reason: format!(
2554                                "constraint index '{}' must be unique and non-partial",
2555                                index
2556                            ),
2557                        };
2558                    }
2559                }
2560
2561                self.snapshot_relation(&alter.id);
2562                let action_type_id = match &alter.action {
2563                    AlterTableActionMutation::AddColumn { ty, .. } => ty
2564                        .as_deref()
2565                        .and_then(|raw| self.resolve_type_reference(raw)),
2566                    AlterTableActionMutation::SetType { ty, .. } => self.resolve_type_reference(ty),
2567                    _ => None,
2568                };
2569                let rel_overlay = self.local.relations.get_mut(&alter.id);
2570                if let Some(RelationOverlay::Present(rel)) = rel_overlay {
2571                    let generation = rel.generation;
2572                    match &alter.action {
2573                        AlterTableActionMutation::AddColumn {
2574                            name,
2575                            ty,
2576                            if_not_exists,
2577                            not_null,
2578                            default,
2579                            depends_on,
2580                            generation: _,
2581                        } => {
2582                            if let Some(existing_col) = rel.columns.iter().find(|c| c.name == *name)
2583                            {
2584                                if *if_not_exists {
2585                                    return MutationResult::Skipped;
2586                                }
2587                                return MutationResult::Conflict {
2588                                    reason: format!(
2589                                        "column '{}' already exists with type {}; this statement adds it again with type {}",
2590                                        name,
2591                                        existing_col.data_type.as_deref().unwrap_or("unknown"),
2592                                        ty.as_deref().unwrap_or("unknown")
2593                                    ),
2594                                };
2595                            }
2596                            rel.apply_column_action(&ColumnAction::Add {
2597                                name: name.clone(),
2598                                data_type: ty.clone(),
2599                                not_null: *not_null,
2600                                default: default.clone(),
2601                            });
2602                            if let Some(column) =
2603                                rel.columns.iter_mut().find(|column| column.name == *name)
2604                            {
2605                                column.type_id = action_type_id.clone();
2606                            }
2607
2608                            if let Some((sequence_id, column_name, _)) = &implicit_add
2609                                && column_name == name
2610                                && let Some(column) =
2611                                    rel.columns.iter_mut().find(|column| column.name == *name)
2612                            {
2613                                column.default = Some(Self::sequence_nextval_default(sequence_id));
2614                                column.default_expr_text = Some(format!(
2615                                    "nextval('{}.{}'::regclass)",
2616                                    sequence_id.schema, sequence_id.name
2617                                ));
2618                                column.is_nullable = false;
2619                            }
2620
2621                            if let Some((source_table, source_col)) = depends_on {
2622                                self.snapshot_graph();
2623                                self.local.graph.edges.push(DependencyEdge::new(
2624                                    alter.id.clone(),
2625                                    source_table.clone(),
2626                                    DependencyKind::ColumnGeneratedFrom {
2627                                        column: name.clone(),
2628                                        depends_on_column: source_col.clone(),
2629                                    },
2630                                ));
2631                            }
2632                        }
2633                        AlterTableActionMutation::DropColumn { name, if_exists } => {
2634                            if !rel.has_column(name) {
2635                                if *if_exists {
2636                                    // Column doesn't exist and IF EXISTS was specified: no-op
2637                                    return MutationResult::Skipped;
2638                                }
2639                                return MutationResult::Conflict {
2640                                    reason: format!(
2641                                        "column '{}' does not exist on relation '{}'",
2642                                        name, alter.id
2643                                    ),
2644                                };
2645                            }
2646                            rel.apply_column_action(&ColumnAction::Drop { name: name.clone() });
2647                        }
2648                        AlterTableActionMutation::RenameColumn { from, to } => {
2649                            rel.apply_column_action(&ColumnAction::Rename {
2650                                from: from.clone(),
2651                                to: to.clone(),
2652                            });
2653                        }
2654                        AlterTableActionMutation::SetNotNull { column } => {
2655                            rel.apply_column_action(&ColumnAction::SetNotNull {
2656                                name: column.clone(),
2657                            });
2658                        }
2659                        AlterTableActionMutation::DropNotNull { column } => {
2660                            rel.apply_column_action(&ColumnAction::DropNotNull {
2661                                name: column.clone(),
2662                            });
2663                        }
2664                        AlterTableActionMutation::SetType { column, ty, .. } => {
2665                            if !rel.has_column(column) {
2666                                self.local.confidence = Confidence::Tainted;
2667                            }
2668                            rel.apply_column_action(&ColumnAction::SetType {
2669                                name: column.clone(),
2670                                data_type: ty.clone(),
2671                            });
2672                            if let Some(column) =
2673                                rel.columns.iter_mut().find(|entry| entry.name == *column)
2674                            {
2675                                column.type_id = action_type_id.clone();
2676                            }
2677                        }
2678                        AlterTableActionMutation::SetDefault { column, default } => {
2679                            if !rel.has_column(column) {
2680                                self.local.confidence = Confidence::Tainted;
2681                            }
2682                            rel.apply_column_action(&ColumnAction::SetDefault {
2683                                name: column.clone(),
2684                                default: default.clone(),
2685                            });
2686                        }
2687                        AlterTableActionMutation::AddForeignKey {
2688                            constraint_name,
2689                            to_table,
2690                            from_columns,
2691                            to_columns,
2692                            not_valid,
2693                        } => {
2694                            let constraint_name = constraint_name.clone().unwrap_or_else(|| {
2695                                format!("{}_{}_fkey", alter.id.name, from_columns.join("_"))
2696                            });
2697                            self.snapshot_constraint(&alter.id, &constraint_name);
2698                            self.local.constraints.insert(
2699                                (alter.id.clone(), constraint_name.clone()),
2700                                ConstraintState {
2701                                    table_id: alter.id.clone(),
2702                                    name: constraint_name.clone(),
2703                                    kind: ConstraintKind::ForeignKey,
2704                                    validated: !not_valid,
2705                                },
2706                            );
2707                            self.snapshot_graph();
2708                            self.local.graph.edges.push(DependencyEdge::new(
2709                                alter.id.clone(),
2710                                to_table.clone(),
2711                                DependencyKind::ForeignKey {
2712                                    constraint_name: Some(constraint_name),
2713                                    from_columns: from_columns.clone(),
2714                                    to_columns: to_columns.clone(),
2715                                    from_generation: generation,
2716                                },
2717                            ));
2718                        }
2719                        AlterTableActionMutation::DropConstraint { name } => {
2720                            self.snapshot_constraint(&alter.id, name);
2721                            self.local
2722                                .constraints
2723                                .remove(&(alter.id.clone(), name.clone()));
2724                            self.snapshot_graph();
2725                            self.local.graph.edges.retain(|e| {
2726                                if let DependencyKind::ForeignKey {
2727                                    constraint_name, ..
2728                                } = &e.kind
2729                                {
2730                                    !(e.dependent == alter.id
2731                                        && constraint_name.as_ref() == Some(name))
2732                                } else {
2733                                    true
2734                                }
2735                            });
2736                        }
2737                        AlterTableActionMutation::RenameConstraint { old_name, new_name } => {
2738                            self.snapshot_constraint(&alter.id, old_name);
2739                            self.snapshot_constraint(&alter.id, new_name);
2740                            if let Some(mut constraint) = self
2741                                .local
2742                                .constraints
2743                                .remove(&(alter.id.clone(), old_name.clone()))
2744                            {
2745                                constraint.name = new_name.clone();
2746                                self.local
2747                                    .constraints
2748                                    .insert((alter.id.clone(), new_name.clone()), constraint);
2749                            }
2750                            self.snapshot_graph_full();
2751                            for edge in &mut self.local.graph.edges {
2752                                if edge.dependent == alter.id
2753                                    && let DependencyKind::ForeignKey {
2754                                        constraint_name, ..
2755                                    } = &mut edge.kind
2756                                    && constraint_name.as_deref() == Some(old_name)
2757                                {
2758                                    *constraint_name = Some(new_name.clone());
2759                                }
2760                            }
2761                        }
2762                        AlterTableActionMutation::AddCheckConstraint {
2763                            constraint_name,
2764                            not_valid,
2765                        } => {
2766                            let constraint_name = constraint_name
2767                                .clone()
2768                                .unwrap_or_else(|| format!("{}_check", alter.id.name));
2769                            self.snapshot_constraint(&alter.id, &constraint_name);
2770                            self.local.constraints.insert(
2771                                (alter.id.clone(), constraint_name.clone()),
2772                                ConstraintState {
2773                                    table_id: alter.id.clone(),
2774                                    name: constraint_name,
2775                                    kind: ConstraintKind::Check,
2776                                    validated: !not_valid,
2777                                },
2778                            );
2779                        }
2780                        AlterTableActionMutation::AddUniqueConstraint {
2781                            constraint_name,
2782                            using_index,
2783                        } => {
2784                            let constraint_name = constraint_name
2785                                .clone()
2786                                .or_else(|| using_index.as_ref().map(|index| index.name.clone()))
2787                                .unwrap_or_else(|| format!("{}_key", alter.id.name));
2788                            self.snapshot_constraint(&alter.id, &constraint_name);
2789                            self.local.constraints.insert(
2790                                (alter.id.clone(), constraint_name.clone()),
2791                                ConstraintState {
2792                                    table_id: alter.id.clone(),
2793                                    name: constraint_name,
2794                                    kind: ConstraintKind::Unique,
2795                                    validated: true,
2796                                },
2797                            );
2798                        }
2799                        AlterTableActionMutation::AddPrimaryKeyConstraint {
2800                            constraint_name,
2801                            using_index,
2802                        } => {
2803                            let constraint_name = constraint_name
2804                                .clone()
2805                                .or_else(|| using_index.as_ref().map(|index| index.name.clone()))
2806                                .unwrap_or_else(|| format!("{}_pkey", alter.id.name));
2807                            self.snapshot_constraint(&alter.id, &constraint_name);
2808                            self.local.constraints.insert(
2809                                (alter.id.clone(), constraint_name.clone()),
2810                                ConstraintState {
2811                                    table_id: alter.id.clone(),
2812                                    name: constraint_name,
2813                                    kind: ConstraintKind::PrimaryKey,
2814                                    validated: true,
2815                                },
2816                            );
2817                        }
2818                        AlterTableActionMutation::AddExcludeConstraint { constraint_name } => {
2819                            let constraint_name = constraint_name
2820                                .clone()
2821                                .unwrap_or_else(|| format!("{}_excl", alter.id.name));
2822                            self.snapshot_constraint(&alter.id, &constraint_name);
2823                            self.local.constraints.insert(
2824                                (alter.id.clone(), constraint_name.clone()),
2825                                ConstraintState {
2826                                    table_id: alter.id.clone(),
2827                                    name: constraint_name,
2828                                    kind: ConstraintKind::Exclusion,
2829                                    validated: true,
2830                                },
2831                            );
2832                        }
2833                        AlterTableActionMutation::ValidateConstraint { constraint_name } => {
2834                            self.snapshot_constraint(&alter.id, constraint_name);
2835                            if let Some(constraint) = self
2836                                .local
2837                                .constraints
2838                                .get_mut(&(alter.id.clone(), constraint_name.clone()))
2839                            {
2840                                constraint.validated = true;
2841                            }
2842                        }
2843                        AlterTableActionMutation::AttachPartition { child, .. } => {
2844                            // BUG-012: Reject cycle topologies before inserting the edge.
2845                            if self.local.graph.check_partition_cycle(&alter.id, child) {
2846                                self.snapshot_confidence();
2847                                self.local.confidence = Confidence::Tainted;
2848                            } else {
2849                                self.snapshot_graph();
2850                                self.local.graph.edges.push(DependencyEdge::new(
2851                                    child.clone(),
2852                                    alter.id.clone(),
2853                                    DependencyKind::PartitionOf,
2854                                ));
2855                            }
2856                        }
2857                        AlterTableActionMutation::DetachPartition { child } => {
2858                            self.snapshot_graph();
2859                            self.local.graph.edges.retain(|e| {
2860                                !(matches!(e.kind, DependencyKind::PartitionOf)
2861                                    && e.dependent == *child
2862                                    && e.referenced == alter.id)
2863                            });
2864                        }
2865                        _ => {}
2866                    }
2867                }
2868                if let Some((sequence_id, column_name, kind)) = implicit_add {
2869                    self.snapshot_sequence(&sequence_id);
2870                    self.snapshot_generation_counter();
2871                    self.local.generation_counter += 1;
2872                    self.local.sequences.insert(
2873                        sequence_id.clone(),
2874                        SequenceOverlay::Present(SequenceState {
2875                            id: sequence_id.clone(),
2876                            owner: self
2877                                .local
2878                                .relations
2879                                .get(&alter.id)
2880                                .and_then(|overlay| match overlay {
2881                                    RelationOverlay::Present(table) => Some(table.owner.clone()),
2882                                    RelationOverlay::Dropped => None,
2883                                })
2884                                .unwrap_or_else(|| ObjectId::new("", &self.local.current_role)),
2885                            owned_by: Some((alter.id.clone(), column_name.clone())),
2886                            kind,
2887                            generation: self.local.generation_counter,
2888                        }),
2889                    );
2890                    self.snapshot_graph();
2891                    self.local.graph.edges.push(DependencyEdge::new(
2892                        sequence_id,
2893                        alter.id.clone(),
2894                        DependencyKind::SequenceOwnedBy {
2895                            column: column_name,
2896                        },
2897                    ));
2898                }
2899                match &alter.action {
2900                    AlterTableActionMutation::DropColumn { .. } => {
2901                        for sequence_id in owned_sequences_for_column {
2902                            self.snapshot_sequence(&sequence_id);
2903                            self.local
2904                                .sequences
2905                                .insert(sequence_id.clone(), SequenceOverlay::Dropped);
2906                            self.snapshot_graph_full();
2907                            self.local.graph.edges.retain(|edge| {
2908                                !(matches!(edge.kind, DependencyKind::SequenceOwnedBy { .. })
2909                                    && edge.dependent == sequence_id)
2910                            });
2911                        }
2912                    }
2913                    AlterTableActionMutation::RenameColumn { to, .. } => {
2914                        for sequence_id in owned_sequences_for_column {
2915                            self.snapshot_sequence(&sequence_id);
2916                            if let Some(SequenceOverlay::Present(sequence)) =
2917                                self.local.sequences.get_mut(&sequence_id)
2918                                && let Some((_, column)) = &mut sequence.owned_by
2919                            {
2920                                *column = to.clone();
2921                            }
2922                            self.snapshot_graph_full();
2923                            for edge in &mut self.local.graph.edges {
2924                                if edge.dependent == sequence_id
2925                                    && let DependencyKind::SequenceOwnedBy { column } =
2926                                        &mut edge.kind
2927                                {
2928                                    *column = to.clone();
2929                                }
2930                            }
2931                        }
2932                    }
2933                    _ => {}
2934                }
2935                MutationResult::Applied
2936            }
2937            Mutation::CreateType(create_type) => {
2938                if self.relation_namespace_is_taken(&create_type.id) {
2939                    return MutationResult::Conflict {
2940                        reason: format!("type '{}' already exists", create_type.id),
2941                    };
2942                }
2943                self.snapshot_type(&create_type.id);
2944                self.snapshot_generation_counter();
2945                self.local.generation_counter += 1;
2946                let generation = self.local.generation_counter;
2947
2948                self.local.types.insert(
2949                    create_type.id.clone(),
2950                    TypeOverlay::Present(TypeState {
2951                        id: create_type.id.clone(),
2952                        generation,
2953                        kind: create_type.kind.clone(),
2954                    }),
2955                );
2956                MutationResult::Applied
2957            }
2958            Mutation::RenameType(rename) => {
2959                if !self.type_is_present(&rename.old_id) {
2960                    if self.baseline_covers_object(&rename.old_id) {
2961                        return MutationResult::Conflict {
2962                            reason: format!("type '{}' does not exist", rename.old_id),
2963                        };
2964                    }
2965                    self.snapshot_confidence();
2966                    self.local.confidence = Confidence::Tainted;
2967                    return MutationResult::Skipped;
2968                }
2969                if rename.old_id != rename.new_id
2970                    && self.relation_namespace_is_taken(&rename.new_id)
2971                {
2972                    return MutationResult::Conflict {
2973                        reason: format!("type '{}' already exists", rename.new_id),
2974                    };
2975                }
2976                if rename.old_id.schema != rename.new_id.schema
2977                    && !self.schema_is_present(&rename.new_id.schema)
2978                {
2979                    if self.schema_absence_is_authoritative(&rename.new_id.schema) {
2980                        return MutationResult::Conflict {
2981                            reason: format!("schema '{}' does not exist", rename.new_id.schema),
2982                        };
2983                    }
2984                    self.snapshot_confidence();
2985                    self.local.confidence = Confidence::Tainted;
2986                    return MutationResult::Skipped;
2987                }
2988
2989                let mut remapped_functions = Vec::new();
2990                for (function_id, overlay) in &self.local.functions {
2991                    let crate::model::function::FunctionOverlay::Present(function) = overlay else {
2992                        continue;
2993                    };
2994                    let new_arg_types = function
2995                        .arg_types
2996                        .iter()
2997                        .enumerate()
2998                        .map(|(index, raw)| {
2999                            if function.arg_type_ids.get(index)
3000                                == Some(&Some(rename.old_id.clone()))
3001                            {
3002                                Self::remapped_type_display(
3003                                    raw,
3004                                    &rename.new_id,
3005                                    rename.old_id.schema != rename.new_id.schema,
3006                                )
3007                            } else {
3008                                raw.clone()
3009                            }
3010                        })
3011                        .collect::<Vec<_>>();
3012                    let new_return_type = if function.return_type_id == Some(rename.old_id.clone())
3013                    {
3014                        Self::remapped_type_display(
3015                            &function.return_type,
3016                            &rename.new_id,
3017                            rename.old_id.schema != rename.new_id.schema,
3018                        )
3019                    } else {
3020                        function.return_type.clone()
3021                    };
3022                    let base_name = function_id
3023                        .name
3024                        .split_once('(')
3025                        .map(|(name, _)| name)
3026                        .unwrap_or(&function_id.name);
3027                    let mut new_function_id = ObjectId::new(
3028                        &function_id.schema,
3029                        format!("{}({})", base_name, new_arg_types.join(",")),
3030                    );
3031                    new_function_id.inferred_schema = function_id.inferred_schema;
3032                    if new_function_id != *function_id
3033                        || new_arg_types != function.arg_types
3034                        || new_return_type != function.return_type
3035                    {
3036                        remapped_functions.push((
3037                            function_id.clone(),
3038                            new_function_id,
3039                            new_arg_types,
3040                            new_return_type,
3041                        ));
3042                    }
3043                }
3044                let moved_function_ids = remapped_functions
3045                    .iter()
3046                    .map(|(old_id, _, _, _)| old_id)
3047                    .collect::<HashSet<_>>();
3048                let mut destinations = HashSet::new();
3049                for (_, new_id, _, _) in &remapped_functions {
3050                    if !destinations.insert(new_id)
3051                        || (self.local.functions.contains_key(new_id)
3052                            && !moved_function_ids.contains(new_id))
3053                    {
3054                        return MutationResult::Conflict {
3055                            reason: format!(
3056                                "routine '{}' already exists after renaming type '{}'",
3057                                new_id, rename.old_id
3058                            ),
3059                        };
3060                    }
3061                }
3062
3063                self.snapshot_namespace();
3064                if let Some(TypeOverlay::Present(mut state)) =
3065                    self.local.types.remove(&rename.old_id)
3066                {
3067                    state.id = rename.new_id.clone();
3068                    self.local
3069                        .types
3070                        .insert(rename.new_id.clone(), TypeOverlay::Present(state));
3071                }
3072                for overlay in self.local.relations.values_mut() {
3073                    if let RelationOverlay::Present(relation) = overlay {
3074                        for column in &mut relation.columns {
3075                            if column.type_id == Some(rename.old_id.clone()) {
3076                                column.data_type = Some(Self::remapped_type_display(
3077                                    column.data_type.as_deref().unwrap_or_default(),
3078                                    &rename.new_id,
3079                                    rename.old_id.schema != rename.new_id.schema,
3080                                ));
3081                                column.type_id = Some(rename.new_id.clone());
3082                            }
3083                        }
3084                    }
3085                }
3086                for overlay in self.local.types.values_mut() {
3087                    if let TypeOverlay::Present(TypeState {
3088                        kind:
3089                            TypeKind::Domain {
3090                                base_type,
3091                                base_type_id,
3092                            },
3093                        ..
3094                    }) = overlay
3095                        && *base_type_id == Some(rename.old_id.clone())
3096                    {
3097                        *base_type = Self::remapped_type_display(
3098                            base_type,
3099                            &rename.new_id,
3100                            rename.old_id.schema != rename.new_id.schema,
3101                        );
3102                        *base_type_id = Some(rename.new_id.clone());
3103                    }
3104                }
3105                for (old_id, new_id, arg_types, return_type) in remapped_functions {
3106                    if let Some(crate::model::function::FunctionOverlay::Present(mut function)) =
3107                        self.local.functions.remove(&old_id)
3108                    {
3109                        function.id = new_id.clone();
3110                        function.arg_types = arg_types;
3111                        for type_id in &mut function.arg_type_ids {
3112                            if *type_id == Some(rename.old_id.clone()) {
3113                                *type_id = Some(rename.new_id.clone());
3114                            }
3115                        }
3116                        function.return_type = return_type;
3117                        if function.return_type_id == Some(rename.old_id.clone()) {
3118                            function.return_type_id = Some(rename.new_id.clone());
3119                        }
3120                        self.local.functions.insert(
3121                            new_id.clone(),
3122                            crate::model::function::FunctionOverlay::Present(function),
3123                        );
3124                        if old_id != new_id {
3125                            self.local.graph.propagate_rename(&old_id, &new_id);
3126                            self.local.graph.edges.push(DependencyEdge::new(
3127                                old_id,
3128                                new_id,
3129                                DependencyKind::RenameTo,
3130                            ));
3131                        }
3132                    }
3133                }
3134                MutationResult::Applied
3135            }
3136            Mutation::AlterType(alter_type) => {
3137                self.snapshot_type(&alter_type.id);
3138                if let Some(TypeOverlay::Present(t)) = self.local.types.get_mut(&alter_type.id) {
3139                    match &alter_type.action {
3140                        AlterTypeActionMutation::AddValue {
3141                            new_value,
3142                            neighbor,
3143                            before,
3144                        } => {
3145                            if let TypeKind::Enum { variants } = &mut t.kind {
3146                                if variants.contains(new_value) {
3147                                    return MutationResult::Skipped;
3148                                }
3149                                let insertion_index = neighbor
3150                                    .as_ref()
3151                                    .and_then(|neighbor| {
3152                                        variants.iter().position(|value| value == neighbor)
3153                                    })
3154                                    .map(|index| if *before { index } else { index + 1 })
3155                                    .unwrap_or(variants.len());
3156                                variants.insert(insertion_index, new_value.clone());
3157                            }
3158                        }
3159                        AlterTypeActionMutation::RenameValue {
3160                            old_value,
3161                            new_value,
3162                        } => {
3163                            let TypeKind::Enum { variants } = &mut t.kind else {
3164                                return MutationResult::Conflict {
3165                                    reason: format!("type '{}' is not an enum", alter_type.id),
3166                                };
3167                            };
3168                            let Some(old_index) =
3169                                variants.iter().position(|value| value == old_value)
3170                            else {
3171                                return MutationResult::Conflict {
3172                                    reason: format!(
3173                                        "'{}' is not an existing label of enum '{}'",
3174                                        old_value, alter_type.id
3175                                    ),
3176                                };
3177                            };
3178                            if variants.iter().any(|value| value == new_value) {
3179                                return MutationResult::Conflict {
3180                                    reason: format!(
3181                                        "enum label '{}' already exists on type '{}'",
3182                                        new_value, alter_type.id
3183                                    ),
3184                                };
3185                            }
3186                            variants[old_index] = new_value.clone();
3187                        }
3188                    }
3189                } else if matches!(
3190                    alter_type.action,
3191                    AlterTypeActionMutation::RenameValue { .. }
3192                ) {
3193                    return MutationResult::Conflict {
3194                        reason: format!("type '{}' does not exist", alter_type.id),
3195                    };
3196                }
3197                MutationResult::Applied
3198            }
3199            Mutation::CreateDomain(create_domain) => {
3200                if self.relation_namespace_is_taken(&create_domain.id) {
3201                    return MutationResult::Conflict {
3202                        reason: format!("type '{}' already exists", create_domain.id),
3203                    };
3204                }
3205                self.snapshot_type(&create_domain.id);
3206                self.snapshot_generation_counter();
3207                self.local.generation_counter += 1;
3208                let generation = self.local.generation_counter;
3209
3210                self.local.types.insert(
3211                    create_domain.id.clone(),
3212                    TypeOverlay::Present(TypeState {
3213                        id: create_domain.id.clone(),
3214                        generation,
3215                        kind: TypeKind::Domain {
3216                            base_type: create_domain.base_type.clone(),
3217                            base_type_id: self.resolve_type_reference(&create_domain.base_type),
3218                        },
3219                    }),
3220                );
3221                MutationResult::Applied
3222            }
3223            Mutation::AlterDomain(_) => MutationResult::Applied,
3224            Mutation::DropDomain(drop_domain) => {
3225                for id in &drop_domain.ids {
3226                    self.snapshot_type(id);
3227                    self.local.types.insert(id.clone(), TypeOverlay::Dropped);
3228                }
3229                MutationResult::Applied
3230            }
3231            Mutation::DropType(drop_type) => {
3232                for id in &drop_type.ids {
3233                    self.snapshot_type(id);
3234                    self.local.types.insert(id.clone(), TypeOverlay::Dropped);
3235                }
3236                MutationResult::Applied
3237            }
3238            Mutation::CreateSequence(create_seq) => {
3239                if create_seq.if_not_exists && self.relation_namespace_is_taken(&create_seq.id) {
3240                    return MutationResult::Skipped;
3241                }
3242                if self.relation_namespace_is_taken(&create_seq.id) {
3243                    return MutationResult::Conflict {
3244                        reason: format!("relation '{}' already exists", create_seq.id),
3245                    };
3246                }
3247                if let Some((table_id, column)) = &create_seq.owned_by {
3248                    if table_id.schema != create_seq.id.schema {
3249                        return MutationResult::Conflict {
3250                            reason: "sequence must be in the same schema as its owning table"
3251                                .to_string(),
3252                        };
3253                    }
3254                    match self.local.relations.get(table_id) {
3255                        Some(RelationOverlay::Present(table)) => {
3256                            if !table.has_column(column) {
3257                                return MutationResult::Conflict {
3258                                    reason: format!(
3259                                        "column '{}.{}' does not exist",
3260                                        table_id, column
3261                                    ),
3262                                };
3263                            }
3264                            if self.local.current_role_known
3265                                && table.owner.name != self.local.current_role
3266                            {
3267                                return MutationResult::Conflict {
3268                                    reason: "sequence and table must have the same owner"
3269                                        .to_string(),
3270                                };
3271                            }
3272                        }
3273                        _ if self.baseline_covers_object(table_id) && self.baseline_available => {
3274                            return MutationResult::Conflict {
3275                                reason: format!("relation '{}' does not exist", table_id),
3276                            };
3277                        }
3278                        _ => {
3279                            self.snapshot_confidence();
3280                            self.local.confidence = Confidence::Tainted;
3281                        }
3282                    }
3283                }
3284                self.snapshot_sequence(&create_seq.id);
3285                self.snapshot_generation_counter();
3286                self.local.generation_counter += 1;
3287                let generation = self.local.generation_counter;
3288
3289                self.local.sequences.insert(
3290                    create_seq.id.clone(),
3291                    SequenceOverlay::Present(SequenceState {
3292                        id: create_seq.id.clone(),
3293                        owner: ObjectId::new("", self.local.current_role.clone()),
3294                        owned_by: create_seq.owned_by.clone(),
3295                        kind: if create_seq.owned_by.is_some() {
3296                            SequenceKind::Owned
3297                        } else {
3298                            SequenceKind::Standalone
3299                        },
3300                        generation,
3301                    }),
3302                );
3303
3304                if let Some((table_id, col)) = &create_seq.owned_by {
3305                    self.snapshot_graph();
3306                    self.local.graph.edges.push(DependencyEdge::new(
3307                        create_seq.id.clone(),
3308                        table_id.clone(),
3309                        DependencyKind::SequenceOwnedBy {
3310                            column: col.clone(),
3311                        },
3312                    ));
3313                }
3314                MutationResult::Applied
3315            }
3316            Mutation::AlterSequence(alter_seq) => {
3317                if !self.sequence_is_present(&alter_seq.id) {
3318                    if alter_seq.if_exists {
3319                        return MutationResult::Skipped;
3320                    }
3321                    if self.baseline_covers_object(&alter_seq.id) && self.baseline_available {
3322                        return MutationResult::Conflict {
3323                            reason: format!("sequence '{}' does not exist", alter_seq.id),
3324                        };
3325                    }
3326                    self.snapshot_confidence();
3327                    self.local.confidence = Confidence::Tainted;
3328                    return MutationResult::Skipped;
3329                }
3330                let current = match self.local.sequences.get(&alter_seq.id) {
3331                    Some(SequenceOverlay::Present(sequence)) => sequence.clone(),
3332                    _ => unreachable!("presence checked above"),
3333                };
3334                match &alter_seq.action {
3335                    crate::analysis::mutations::AlterSequenceActionMutation::OwnedBy(owned_by) => {
3336                        if current.kind == SequenceKind::Identity {
3337                            return MutationResult::Conflict {
3338                                reason: "cannot change ownership of an identity sequence"
3339                                    .to_string(),
3340                            };
3341                        }
3342                        if let Some((table_id, column)) = owned_by {
3343                            if table_id.schema != alter_seq.id.schema {
3344                                return MutationResult::Conflict {
3345                                    reason:
3346                                        "sequence must be in the same schema as its owning table"
3347                                            .to_string(),
3348                                };
3349                            }
3350                            let Some(RelationOverlay::Present(table)) =
3351                                self.local.relations.get(table_id)
3352                            else {
3353                                return MutationResult::Conflict {
3354                                    reason: format!("relation '{}' does not exist", table_id),
3355                                };
3356                            };
3357                            if !table.has_column(column) {
3358                                return MutationResult::Conflict {
3359                                    reason: format!(
3360                                        "column '{}.{}' does not exist",
3361                                        table_id, column
3362                                    ),
3363                                };
3364                            }
3365                            if table.owner != current.owner {
3366                                return MutationResult::Conflict {
3367                                    reason: "sequence and table must have the same owner"
3368                                        .to_string(),
3369                                };
3370                            }
3371                        }
3372                        self.snapshot_sequence(&alter_seq.id);
3373                        self.snapshot_graph();
3374                        self.local.graph.edges.retain(|edge| {
3375                            !(matches!(edge.kind, DependencyKind::SequenceOwnedBy { .. })
3376                                && edge.dependent == alter_seq.id)
3377                        });
3378                        if let Some(SequenceOverlay::Present(sequence)) =
3379                            self.local.sequences.get_mut(&alter_seq.id)
3380                        {
3381                            sequence.owned_by = owned_by.clone();
3382                            sequence.kind = if owned_by.is_some() {
3383                                SequenceKind::Owned
3384                            } else {
3385                                SequenceKind::Standalone
3386                            };
3387                        }
3388                        if let Some((table_id, column)) = owned_by {
3389                            self.local.graph.edges.push(DependencyEdge::new(
3390                                alter_seq.id.clone(),
3391                                table_id.clone(),
3392                                DependencyKind::SequenceOwnedBy {
3393                                    column: column.clone(),
3394                                },
3395                            ));
3396                        }
3397                        MutationResult::Applied
3398                    }
3399                    crate::analysis::mutations::AlterSequenceActionMutation::OwnerTo(owner) => {
3400                        if current.kind == SequenceKind::Identity {
3401                            return MutationResult::Conflict {
3402                                reason: "cannot alter an identity sequence independently"
3403                                    .to_string(),
3404                            };
3405                        }
3406                        let Some((owner_name, known)) = self.role_fact_identity(owner) else {
3407                            self.snapshot_confidence();
3408                            self.local.confidence = Confidence::Tainted;
3409                            return MutationResult::Skipped;
3410                        };
3411                        if known
3412                            && self.local.roles_known
3413                            && self.present_role(&owner_name).is_none()
3414                        {
3415                            return MutationResult::Conflict {
3416                                reason: format!("role '{}' does not exist", owner_name),
3417                            };
3418                        }
3419                        if let Some((table_id, _)) = &current.owned_by
3420                            && let Some(RelationOverlay::Present(table)) =
3421                                self.local.relations.get(table_id)
3422                            && table.owner.name != owner_name
3423                        {
3424                            return MutationResult::Conflict {
3425                                reason: "sequence and table must have the same owner".to_string(),
3426                            };
3427                        }
3428                        self.snapshot_sequence(&alter_seq.id);
3429                        if let Some(SequenceOverlay::Present(sequence)) =
3430                            self.local.sequences.get_mut(&alter_seq.id)
3431                        {
3432                            sequence.owner = ObjectId::new("", owner_name);
3433                        }
3434                        MutationResult::Applied
3435                    }
3436                    crate::analysis::mutations::AlterSequenceActionMutation::RenameTo(new_id)
3437                    | crate::analysis::mutations::AlterSequenceActionMutation::SetSchema(new_id) => {
3438                        if current.kind == SequenceKind::Identity {
3439                            return MutationResult::Conflict {
3440                                reason: "cannot alter an identity sequence independently"
3441                                    .to_string(),
3442                            };
3443                        }
3444                        if self.relation_namespace_is_taken(new_id) {
3445                            return MutationResult::Conflict {
3446                                reason: format!("relation '{}' already exists", new_id),
3447                            };
3448                        }
3449                        if let Some((table_id, _)) = &current.owned_by
3450                            && table_id.schema != new_id.schema
3451                        {
3452                            return MutationResult::Conflict {
3453                                reason: "sequence must be in the same schema as its owning table"
3454                                    .to_string(),
3455                            };
3456                        }
3457                        self.snapshot_namespace();
3458                        let mut moved = current;
3459                        moved.id = new_id.clone();
3460                        self.local.sequences.remove(&alter_seq.id);
3461                        self.local
3462                            .sequences
3463                            .insert(new_id.clone(), SequenceOverlay::Present(moved));
3464                        self.local.graph.propagate_rename(&alter_seq.id, new_id);
3465                        self.local.graph.edges.push(DependencyEdge::new(
3466                            alter_seq.id.clone(),
3467                            new_id.clone(),
3468                            DependencyKind::RenameTo,
3469                        ));
3470                        if self.baseline_sequences.remove(&alter_seq.id) {
3471                            self.baseline_sequences.insert(new_id.clone());
3472                        }
3473                        MutationResult::Applied
3474                    }
3475                    crate::analysis::mutations::AlterSequenceActionMutation::Other => {
3476                        MutationResult::Applied
3477                    }
3478                }
3479            }
3480            Mutation::DropSequence(drop_seq) => {
3481                if !drop_seq.if_exists {
3482                    let missing: Vec<ObjectId> = drop_seq
3483                        .ids
3484                        .iter()
3485                        .filter(|id| !self.sequence_is_present(id))
3486                        .cloned()
3487                        .collect();
3488                    for id in &missing {
3489                        if self.baseline_covers_object(id) && self.baseline_available {
3490                            return MutationResult::Conflict {
3491                                reason: format!("sequence '{}' does not exist", id),
3492                            };
3493                        }
3494                        self.snapshot_confidence();
3495                        self.local.confidence = Confidence::Tainted;
3496                    }
3497                }
3498                let present: Vec<ObjectId> = drop_seq
3499                    .ids
3500                    .iter()
3501                    .filter(|id| self.sequence_is_present(id))
3502                    .cloned()
3503                    .collect();
3504                if present.is_empty() {
3505                    return MutationResult::Skipped;
3506                }
3507                for id in &present {
3508                    let Some(SequenceOverlay::Present(sequence)) = self.local.sequences.get(id)
3509                    else {
3510                        continue;
3511                    };
3512                    if sequence.kind == SequenceKind::Identity {
3513                        return MutationResult::Conflict {
3514                            reason: format!("cannot drop identity sequence '{}' independently", id),
3515                        };
3516                    }
3517                    if sequence.kind == SequenceKind::SerialLike && !drop_seq.cascade {
3518                        return MutationResult::Conflict {
3519                            reason: format!("sequence '{}' still has dependent defaults", id),
3520                        };
3521                    }
3522                }
3523                if drop_seq.cascade {
3524                    let serial_owners: Vec<(ObjectId, String)> = present
3525                        .iter()
3526                        .filter_map(|id| match self.local.sequences.get(id) {
3527                            Some(SequenceOverlay::Present(sequence))
3528                                if sequence.kind == SequenceKind::SerialLike =>
3529                            {
3530                                sequence.owned_by.clone()
3531                            }
3532                            _ => None,
3533                        })
3534                        .collect();
3535                    for (table_id, column) in serial_owners {
3536                        self.snapshot_relation(&table_id);
3537                        if let Some(RelationOverlay::Present(table)) =
3538                            self.local.relations.get_mut(&table_id)
3539                            && let Some(column) =
3540                                table.columns.iter_mut().find(|item| item.name == column)
3541                        {
3542                            column.default = None;
3543                            column.default_expr_text = None;
3544                        }
3545                    }
3546                }
3547                for id in &present {
3548                    self.snapshot_sequence(id);
3549                    self.local
3550                        .sequences
3551                        .insert(id.clone(), SequenceOverlay::Dropped);
3552                }
3553                self.snapshot_graph_full();
3554                self.local.graph.edges.retain(|e| {
3555                    !(matches!(e.kind, DependencyKind::SequenceOwnedBy { .. })
3556                        && present.contains(&e.dependent))
3557                });
3558                MutationResult::Applied
3559            }
3560            Mutation::Rename(rename) => {
3561                let renames_relation = self.relation_is_present(&rename.old_id);
3562                let renames_index = self.index_is_present(&rename.old_id);
3563                if !renames_relation && !renames_index {
3564                    if self.baseline_covers_object(&rename.old_id) {
3565                        return MutationResult::Conflict {
3566                            reason: format!("relation '{}' does not exist", rename.old_id),
3567                        };
3568                    }
3569                    self.snapshot_confidence();
3570                    self.local.confidence = Confidence::Tainted;
3571                    return MutationResult::Skipped;
3572                }
3573                if rename.old_id != rename.new_id
3574                    && self.relation_namespace_is_taken(&rename.new_id)
3575                {
3576                    return MutationResult::Conflict {
3577                        reason: format!("relation '{}' already exists", rename.new_id),
3578                    };
3579                }
3580                if rename.old_id.schema != rename.new_id.schema
3581                    && !self.schema_is_present(&rename.new_id.schema)
3582                {
3583                    if self.schema_absence_is_authoritative(&rename.new_id.schema) {
3584                        return MutationResult::Conflict {
3585                            reason: format!("schema '{}' does not exist", rename.new_id.schema),
3586                        };
3587                    }
3588                    self.snapshot_confidence();
3589                    self.local.confidence = Confidence::Tainted;
3590                    return MutationResult::Skipped;
3591                }
3592
3593                self.snapshot_namespace();
3594                if let Some(RelationOverlay::Present(mut state)) =
3595                    self.local.relations.remove(&rename.old_id)
3596                {
3597                    state.id = rename.new_id.clone();
3598                    self.local
3599                        .relations
3600                        .insert(rename.new_id.clone(), RelationOverlay::Present(state));
3601                }
3602                let owned_sequence_ids: Vec<ObjectId> = self
3603                    .local
3604                    .sequences
3605                    .iter()
3606                    .filter_map(|(id, overlay)| match overlay {
3607                        SequenceOverlay::Present(sequence)
3608                            if sequence
3609                                .owned_by
3610                                .as_ref()
3611                                .is_some_and(|(table, _)| table == &rename.old_id) =>
3612                        {
3613                            Some(id.clone())
3614                        }
3615                        _ => None,
3616                    })
3617                    .collect();
3618                for sequence_id in owned_sequence_ids {
3619                    self.snapshot_sequence(&sequence_id);
3620                    if let Some(SequenceOverlay::Present(sequence)) =
3621                        self.local.sequences.get_mut(&sequence_id)
3622                        && let Some((table, _)) = &mut sequence.owned_by
3623                    {
3624                        *table = rename.new_id.clone();
3625                    }
3626                }
3627                let triggers_to_move: Vec<(ObjectId, crate::model::trigger::TriggerState)> = self
3628                    .local
3629                    .triggers
3630                    .iter()
3631                    .filter_map(|(id, overlay)| match overlay {
3632                        TriggerOverlay::Present(trigger) if trigger.table_id == rename.old_id => {
3633                            Some((id.clone(), trigger.clone()))
3634                        }
3635                        _ => None,
3636                    })
3637                    .collect();
3638                for (old_trigger_id, mut trigger) in triggers_to_move {
3639                    let new_trigger_id = Self::trigger_key(&rename.new_id, &trigger.name);
3640                    self.local.triggers.remove(&old_trigger_id);
3641                    trigger.id = new_trigger_id.clone();
3642                    trigger.table_id = rename.new_id.clone();
3643                    self.local
3644                        .triggers
3645                        .insert(new_trigger_id.clone(), TriggerOverlay::Present(trigger));
3646                    self.local
3647                        .graph
3648                        .propagate_rename(&old_trigger_id, &new_trigger_id);
3649                    self.local.graph.edges.push(DependencyEdge::new(
3650                        old_trigger_id,
3651                        new_trigger_id,
3652                        DependencyKind::RenameTo,
3653                    ));
3654                }
3655                let constraints_to_move: Vec<(String, ConstraintState)> = self
3656                    .local
3657                    .constraints
3658                    .iter()
3659                    .filter(|((table_id, _), _)| table_id == &rename.old_id)
3660                    .map(|((_, name), constraint)| (name.clone(), constraint.clone()))
3661                    .collect();
3662                for (name, mut constraint) in constraints_to_move {
3663                    self.snapshot_constraint(&rename.old_id, &name);
3664                    self.snapshot_constraint(&rename.new_id, &name);
3665                    self.local
3666                        .constraints
3667                        .remove(&(rename.old_id.clone(), name.clone()));
3668                    constraint.table_id = rename.new_id.clone();
3669                    self.local
3670                        .constraints
3671                        .insert((rename.new_id.clone(), name), constraint);
3672                }
3673                self.local.pending_validation = std::mem::take(&mut self.local.pending_validation)
3674                    .into_iter()
3675                    .map(|(table, name)| {
3676                        if table == rename.old_id {
3677                            (rename.new_id.clone(), name)
3678                        } else {
3679                            (table, name)
3680                        }
3681                    })
3682                    .collect();
3683                self.local.graph.edges.push(DependencyEdge::new(
3684                    rename.old_id.clone(),
3685                    rename.new_id.clone(),
3686                    DependencyKind::RenameTo,
3687                ));
3688                self.local
3689                    .graph
3690                    .propagate_rename(&rename.old_id, &rename.new_id);
3691
3692                if renames_relation {
3693                    if self.baseline_relations.remove(&rename.old_id) {
3694                        self.baseline_relations.insert(rename.new_id.clone());
3695                    }
3696                    if self.baseline_fk_dependencies.remove(&rename.old_id) {
3697                        self.baseline_fk_dependencies.insert(rename.new_id.clone());
3698                    }
3699                    self.baseline_foreign_keys = std::mem::take(&mut self.baseline_foreign_keys)
3700                        .into_iter()
3701                        .map(|(table, name)| {
3702                            if table == rename.old_id {
3703                                (rename.new_id.clone(), name)
3704                            } else {
3705                                (table, name)
3706                            }
3707                        })
3708                        .collect();
3709                }
3710                if renames_index && self.baseline_indexes.remove(&rename.old_id) {
3711                    self.baseline_indexes.insert(rename.new_id.clone());
3712                }
3713
3714                MutationResult::Applied
3715            }
3716            Mutation::DropView(drop_view) => {
3717                for id in &drop_view.ids {
3718                    self.snapshot_relation(id);
3719                    self.local
3720                        .relations
3721                        .insert(id.clone(), RelationOverlay::Dropped);
3722                }
3723                self.snapshot_graph_full();
3724                self.local.graph.edges.retain(|e| {
3725                    !(matches!(e.kind, DependencyKind::ViewDependency { .. })
3726                        && drop_view.ids.contains(&e.dependent))
3727                });
3728                MutationResult::Applied
3729            }
3730            Mutation::DropMaterializedView(drop_mv) => {
3731                for id in &drop_mv.ids {
3732                    self.snapshot_relation(id);
3733                    self.local
3734                        .relations
3735                        .insert(id.clone(), RelationOverlay::Dropped);
3736                }
3737                self.snapshot_graph_full();
3738                self.local.graph.edges.retain(|e| {
3739                    !((matches!(e.kind, DependencyKind::ViewDependency { .. })
3740                        && drop_mv.ids.contains(&e.dependent))
3741                        || (matches!(e.kind, DependencyKind::IndexOnRelation { .. })
3742                            && drop_mv.ids.contains(&e.referenced)))
3743                });
3744                MutationResult::Applied
3745            }
3746            Mutation::DropIndex(drop_idx) => {
3747                self.snapshot_graph();
3748                self.local.graph.edges.retain(|e| {
3749                    !(matches!(e.kind, DependencyKind::IndexOnRelation { .. })
3750                        && e.dependent == drop_idx.id)
3751                });
3752                MutationResult::Applied
3753            }
3754            Mutation::ChangeRelationOwner { id, new_owner } => {
3755                let Some((owner, known)) = self.role_fact_identity(new_owner) else {
3756                    self.snapshot_confidence();
3757                    self.local.confidence = Confidence::Tainted;
3758                    return MutationResult::Skipped;
3759                };
3760                if !known {
3761                    self.snapshot_confidence();
3762                    self.local.confidence = Confidence::Tainted;
3763                }
3764                self.snapshot_relation(id);
3765                if let Some(RelationOverlay::Present(relation)) = self.local.relations.get_mut(id) {
3766                    relation.owner = ObjectId::new("", owner);
3767                    MutationResult::Applied
3768                } else {
3769                    MutationResult::Conflict {
3770                        reason: format!("relation '{}' does not exist", id),
3771                    }
3772                }
3773            }
3774            Mutation::SearchPath(sp) => {
3775                self.snapshot_search_path();
3776                self.snapshot_confidence();
3777                match &sp.target {
3778                    SearchPathTarget::Default => {
3779                        self.local.search_path_template =
3780                            self.local.default_search_path_template.clone();
3781                        self.refresh_role_sensitive_search_path();
3782                    }
3783                    SearchPathTarget::Schemas(schemas) => {
3784                        self.local.search_path_template = schemas.clone();
3785                        self.refresh_role_sensitive_search_path();
3786                    }
3787                }
3788                MutationResult::Applied
3789            }
3790            Mutation::SwitchRole {
3791                role,
3792                local,
3793                is_session_auth,
3794            } => {
3795                if *local && self.local.transactions.is_empty() {
3796                    // PostgreSQL warns and leaves the setting unchanged.
3797                    return MutationResult::Skipped;
3798                }
3799
3800                let target = if let Some(role) = role {
3801                    let Some(identity) = self.role_fact_identity(role) else {
3802                        self.snapshot_confidence();
3803                        self.local.confidence = Confidence::Tainted;
3804                        return MutationResult::Skipped;
3805                    };
3806                    Some(identity)
3807                } else if *is_session_auth {
3808                    Some((
3809                        self.local.authenticated_role.clone(),
3810                        self.local.authenticated_role_known,
3811                    ))
3812                } else {
3813                    Some((
3814                        self.local.session_role.clone(),
3815                        self.local.session_role_known,
3816                    ))
3817                };
3818                let (target_name, target_known) = target.expect("role reset always has a target");
3819                let persistent_role_reset_target = if role.is_none() && !*is_session_auth {
3820                    Some((
3821                        self.local.persistent_session_role.clone(),
3822                        self.local.persistent_session_role_known,
3823                    ))
3824                } else {
3825                    None
3826                };
3827
3828                let authorized = if role.is_none() {
3829                    Some(true)
3830                } else if *is_session_auth {
3831                    self.can_set_session_authorization_to(&target_name)
3832                } else {
3833                    self.can_set_role_to(&target_name)
3834                };
3835                match authorized {
3836                    Some(false) => {
3837                        return MutationResult::Conflict {
3838                            reason: if self.present_role(&target_name).is_none() {
3839                                format!("role '{}' does not exist", target_name)
3840                            } else {
3841                                format!("permission denied to set role '{}'", target_name)
3842                            },
3843                        };
3844                    }
3845                    None => {
3846                        self.snapshot_confidence();
3847                        self.local.confidence = Confidence::Tainted;
3848                    }
3849                    Some(true) => {}
3850                }
3851
3852                self.snapshot_role_context();
3853                self.snapshot_search_path();
3854                self.snapshot_confidence();
3855                if *is_session_auth {
3856                    self.local.session_role = target_name.clone();
3857                    self.local.session_role_known = target_known;
3858                    self.local.current_role = target_name.clone();
3859                    self.local.current_role_known = target_known;
3860                    if !local {
3861                        self.local.persistent_session_role = target_name.clone();
3862                        self.local.persistent_session_role_known = target_known;
3863                        self.local.persistent_current_role = target_name;
3864                        self.local.persistent_current_role_known = target_known;
3865                    }
3866                } else {
3867                    self.local.current_role = target_name.clone();
3868                    self.local.current_role_known = target_known;
3869                    if !local {
3870                        let (persistent_name, persistent_known) =
3871                            persistent_role_reset_target.unwrap_or((target_name, target_known));
3872                        self.local.persistent_current_role = persistent_name;
3873                        self.local.persistent_current_role_known = persistent_known;
3874                    }
3875                }
3876                self.refresh_role_sensitive_search_path();
3877                MutationResult::Applied
3878            }
3879            Mutation::BeginTransaction => {
3880                if self.local.transactions.is_empty() {
3881                    self.local.transactions.push(TransactionFrame::root());
3882                    MutationResult::Applied
3883                } else {
3884                    // PostgreSQL emits a warning and leaves the current
3885                    // transaction active for a nested BEGIN.
3886                    MutationResult::Skipped
3887                }
3888            }
3889            Mutation::CommitTransaction => {
3890                if self.local.transaction_aborted {
3891                    while let Some(frame) = self.local.transactions.pop() {
3892                        self.rollback_frame(frame);
3893                    }
3894                } else {
3895                    while self.local.transactions.pop().is_some() {}
3896                    self.restore_persistent_role_context();
3897                }
3898                self.local.transaction_aborted = false;
3899                MutationResult::Applied
3900            }
3901            Mutation::CommitAndChain => {
3902                if self.local.transactions.is_empty() {
3903                    self.local.confidence = Confidence::Tainted;
3904                    return MutationResult::Conflict {
3905                        reason: "COMMIT AND CHAIN can only be used in transaction blocks"
3906                            .to_string(),
3907                    };
3908                }
3909                if self.local.transaction_aborted {
3910                    while let Some(frame) = self.local.transactions.pop() {
3911                        self.rollback_frame(frame);
3912                    }
3913                } else {
3914                    while self.local.transactions.pop().is_some() {}
3915                    self.restore_persistent_role_context();
3916                }
3917                self.local.transaction_aborted = false;
3918                self.local.transactions.push(TransactionFrame::root());
3919                MutationResult::Applied
3920            }
3921            Mutation::RollbackTransaction => {
3922                while let Some(frame) = self.local.transactions.pop() {
3923                    self.rollback_frame(frame);
3924                }
3925                self.local.transaction_aborted = false;
3926                MutationResult::Applied
3927            }
3928            Mutation::RollbackAndChain => {
3929                if self.local.transactions.is_empty() {
3930                    self.local.confidence = Confidence::Tainted;
3931                    return MutationResult::Conflict {
3932                        reason: "ROLLBACK AND CHAIN can only be used in transaction blocks"
3933                            .to_string(),
3934                    };
3935                }
3936                while let Some(frame) = self.local.transactions.pop() {
3937                    self.rollback_frame(frame);
3938                }
3939                self.local.transaction_aborted = false;
3940                self.local.transactions.push(TransactionFrame::root());
3941                MutationResult::Applied
3942            }
3943            Mutation::RollbackToSavepoint(rts) => {
3944                let Some(position) = self
3945                    .local
3946                    .transactions
3947                    .iter()
3948                    .rposition(|frame| frame.is_named_savepoint(&rts.name))
3949                else {
3950                    self.local.confidence = Confidence::Tainted;
3951                    if !self.local.transactions.is_empty() {
3952                        self.local.transaction_aborted = true;
3953                    }
3954                    return MutationResult::Conflict {
3955                        reason: format!("savepoint '{}' does not exist", rts.name),
3956                    };
3957                };
3958                let rolled_back = self.local.transactions.split_off(position + 1);
3959                // Frames are popped newest-first. Restore them in that same
3960                // order before restoring changes made after the target
3961                // savepoint itself; undo logs are chronological.
3962                for frame in rolled_back.into_iter().rev() {
3963                    self.rollback_frame(frame);
3964                }
3965                let undo_log = std::mem::take(&mut self.local.transactions[position].undo_log);
3966                self.rollback_undo_log(undo_log);
3967                self.local.transaction_aborted = false;
3968                MutationResult::Applied
3969            }
3970            Mutation::Savepoint(sp) => {
3971                if self.local.transactions.is_empty() {
3972                    self.local.confidence = Confidence::Tainted;
3973                    return MutationResult::Conflict {
3974                        reason: "SAVEPOINT can only be used in transaction blocks".to_string(),
3975                    };
3976                }
3977                self.local
3978                    .transactions
3979                    .push(TransactionFrame::savepoint(sp.name.clone()));
3980                MutationResult::Applied
3981            }
3982            Mutation::ReleaseSavepoint(rsp) => {
3983                let Some(position) = self
3984                    .local
3985                    .transactions
3986                    .iter()
3987                    .rposition(|frame| frame.is_named_savepoint(&rsp.name))
3988                else {
3989                    self.local.confidence = Confidence::Tainted;
3990                    if !self.local.transactions.is_empty() {
3991                        self.local.transaction_aborted = true;
3992                    }
3993                    return MutationResult::Conflict {
3994                        reason: format!("savepoint '{}' does not exist", rsp.name),
3995                    };
3996                };
3997                if position == 0 {
3998                    self.local.confidence = Confidence::Tainted;
3999                    return MutationResult::Conflict {
4000                        reason: format!("savepoint '{}' is not inside a transaction", rsp.name),
4001                    };
4002                }
4003
4004                let released = self.local.transactions.split_off(position);
4005                let outer = self
4006                    .local
4007                    .transactions
4008                    .last_mut()
4009                    .expect("a released savepoint always has an outer transaction frame");
4010                for frame in released {
4011                    outer.undo_log.extend(frame.undo_log);
4012                }
4013                MutationResult::Applied
4014            }
4015            Mutation::Opaque(_) => {
4016                self.snapshot_confidence();
4017                self.local.confidence = Confidence::Tainted;
4018                MutationResult::Applied
4019            }
4020            Mutation::CreateFunction(f) => {
4021                if matches!(
4022                    self.local.functions.get(&f.id),
4023                    Some(crate::model::function::FunctionOverlay::Present(_))
4024                ) && !f.or_replace
4025                {
4026                    return MutationResult::Conflict {
4027                        reason: format!("routine '{}' already exists", f.id),
4028                    };
4029                }
4030                self.snapshot_function(&f.id);
4031                self.snapshot_generation_counter();
4032                self.local.generation_counter += 1;
4033                let _generation = self.local.generation_counter;
4034
4035                let volatility = f
4036                    .options
4037                    .iter()
4038                    .find_map(|opt| {
4039                        if let crate::analysis::facts::FuncOptionFact::Volatility(v) = opt {
4040                            Some(match v {
4041                                crate::analysis::facts::VolatilityKind::Volatile => {
4042                                    crate::model::function::Volatility::Volatile
4043                                }
4044                                crate::analysis::facts::VolatilityKind::Stable => {
4045                                    crate::model::function::Volatility::Stable
4046                                }
4047                                crate::analysis::facts::VolatilityKind::Immutable => {
4048                                    crate::model::function::Volatility::Immutable
4049                                }
4050                            })
4051                        } else {
4052                            None
4053                        }
4054                    })
4055                    .unwrap_or(crate::model::function::Volatility::Volatile);
4056
4057                let security = f
4058                    .options
4059                    .iter()
4060                    .find_map(|opt| {
4061                        if let crate::analysis::facts::FuncOptionFact::Security(s) = opt {
4062                            Some(match s {
4063                                crate::analysis::facts::SecurityKind::Invoker => {
4064                                    crate::model::function::SecurityMode::Invoker
4065                                }
4066                                crate::analysis::facts::SecurityKind::Definer => {
4067                                    crate::model::function::SecurityMode::Definer
4068                                }
4069                            })
4070                        } else {
4071                            None
4072                        }
4073                    })
4074                    .unwrap_or(crate::model::function::SecurityMode::Invoker);
4075
4076                let language = f
4077                    .options
4078                    .iter()
4079                    .find_map(|opt| {
4080                        if let crate::analysis::facts::FuncOptionFact::Language(l) = opt {
4081                            Some(l.clone())
4082                        } else {
4083                            None
4084                        }
4085                    })
4086                    .unwrap_or_else(|| "sql".to_string());
4087
4088                self.local.functions.insert(
4089                    f.id.clone(),
4090                    crate::model::function::FunctionOverlay::Present(
4091                        crate::model::function::FunctionState {
4092                            id: f.id.clone(),
4093                            arg_types: f.params.iter().map(|p| p.ty.clone()).collect(),
4094                            arg_type_ids: f
4095                                .params
4096                                .iter()
4097                                .map(|parameter| self.resolve_type_reference(&parameter.ty))
4098                                .collect(),
4099                            return_type: f
4100                                .return_type
4101                                .as_ref()
4102                                .map(|rt| match rt {
4103                                    crate::analysis::facts::RetTypeFact::Scalar(ty) => ty.clone(),
4104                                    crate::analysis::facts::RetTypeFact::Table(columns) => columns
4105                                        .iter()
4106                                        .map(|column| {
4107                                            format!(
4108                                                "{} {}",
4109                                                column.name,
4110                                                column.ty.as_deref().unwrap_or("unknown")
4111                                            )
4112                                        })
4113                                        .collect::<Vec<_>>()
4114                                        .join(", "),
4115                                })
4116                                .unwrap_or_default(),
4117                            return_type_id: f.return_type.as_ref().and_then(|return_type| {
4118                                match return_type {
4119                                    crate::analysis::facts::RetTypeFact::Scalar(ty) => {
4120                                        self.resolve_type_reference(ty)
4121                                    }
4122                                    crate::analysis::facts::RetTypeFact::Table(_) => None,
4123                                }
4124                            }),
4125                            volatility,
4126                            language,
4127                            security,
4128                        },
4129                    ),
4130                );
4131                MutationResult::Applied
4132            }
4133            Mutation::AlterFunction(f) => {
4134                use crate::analysis::facts::{AlterFunctionAction, FuncOptionFact};
4135                use crate::model::function::{FunctionOverlay, SecurityMode, Volatility};
4136
4137                match &f.action {
4138                    AlterFunctionAction::OptionsChange(options) => {
4139                        self.snapshot_function(&f.id);
4140                        if let Some(FunctionOverlay::Present(function)) =
4141                            self.local.functions.get_mut(&f.id)
4142                        {
4143                            for option in options {
4144                                match option {
4145                                    FuncOptionFact::Volatility(volatility) => {
4146                                        function.volatility = match volatility {
4147                                            crate::analysis::facts::VolatilityKind::Volatile => {
4148                                                Volatility::Volatile
4149                                            }
4150                                            crate::analysis::facts::VolatilityKind::Stable => {
4151                                                Volatility::Stable
4152                                            }
4153                                            crate::analysis::facts::VolatilityKind::Immutable => {
4154                                                Volatility::Immutable
4155                                            }
4156                                        };
4157                                    }
4158                                    FuncOptionFact::Security(security) => {
4159                                        function.security = match security {
4160                                            crate::analysis::facts::SecurityKind::Invoker => {
4161                                                SecurityMode::Invoker
4162                                            }
4163                                            crate::analysis::facts::SecurityKind::Definer => {
4164                                                SecurityMode::Definer
4165                                            }
4166                                        };
4167                                    }
4168                                    FuncOptionFact::Language(language) => {
4169                                        function.language = language.clone();
4170                                    }
4171                                    _ => {}
4172                                }
4173                            }
4174                        }
4175                    }
4176                    AlterFunctionAction::Rename { to, .. } => {
4177                        let signature =
4178                            f.id.name
4179                                .find('(')
4180                                .map(|index| &f.id.name[index..])
4181                                .unwrap_or("");
4182                        let new_id = ObjectId::new(f.id.schema.clone(), format!("{to}{signature}"));
4183                        self.move_function(&f.id, &new_id);
4184                    }
4185                    AlterFunctionAction::SchemaChange { new_schema } => {
4186                        let new_id = ObjectId::new(new_schema.clone(), f.id.name.clone());
4187                        self.move_function(&f.id, &new_id);
4188                    }
4189                    AlterFunctionAction::OwnerChange(_)
4190                    | AlterFunctionAction::DependsOnExtension { .. }
4191                    | AlterFunctionAction::NoDependsOnExtension { .. } => {
4192                        self.snapshot_function(&f.id);
4193                    }
4194                }
4195                MutationResult::Applied
4196            }
4197            Mutation::DropFunction(f) => {
4198                let mut any_applied = false;
4199                for sig in &f.signatures {
4200                    let sig_str = format!("{}({})", sig.name.name.resolve(), sig.params.join(","));
4201                    let schema = self.resolve_function_schema(&sig.name, &sig_str);
4202                    let id = ObjectId::new(schema, sig_str);
4203                    if !matches!(
4204                        self.local.functions.get(&id),
4205                        Some(crate::model::function::FunctionOverlay::Present(_))
4206                    ) {
4207                        if !f.if_exists {
4208                            return MutationResult::Conflict {
4209                                reason: format!("function '{}' does not exist", id),
4210                            };
4211                        }
4212                    } else {
4213                        let dependent_triggers: Vec<(ObjectId, ObjectId)> = self
4214                            .local
4215                            .graph
4216                            .edges
4217                            .iter()
4218                            .filter_map(|edge| {
4219                                let DependencyKind::TriggerOnTable { function_id, .. } = &edge.kind
4220                                else {
4221                                    return None;
4222                                };
4223                                (function_id == &id)
4224                                    .then(|| (edge.dependent.clone(), edge.referenced.clone()))
4225                            })
4226                            .collect();
4227                        if !dependent_triggers.is_empty() && !f.cascade {
4228                            return MutationResult::Conflict {
4229                                reason: format!(
4230                                    "function '{}' still has dependent triggers; use CASCADE",
4231                                    id
4232                                ),
4233                            };
4234                        }
4235
4236                        any_applied = true;
4237                        self.snapshot_function(&id);
4238                        self.local
4239                            .functions
4240                            .insert(id.clone(), crate::model::function::FunctionOverlay::Dropped);
4241
4242                        if f.cascade {
4243                            for (trigger_id, table_id) in &dependent_triggers {
4244                                let trigger_name =
4245                                    self.local.triggers.get(trigger_id).and_then(|overlay| {
4246                                        match overlay {
4247                                            TriggerOverlay::Present(trigger) => {
4248                                                Some(trigger.name.clone())
4249                                            }
4250                                            TriggerOverlay::Dropped => None,
4251                                        }
4252                                    });
4253                                self.snapshot_trigger(trigger_id);
4254                                self.local
4255                                    .triggers
4256                                    .insert(trigger_id.clone(), TriggerOverlay::Dropped);
4257                                self.snapshot_relation(table_id);
4258                                if let Some(RelationOverlay::Present(relation)) =
4259                                    self.local.relations.get_mut(table_id)
4260                                    && let Some(trigger_name) = trigger_name
4261                                {
4262                                    relation.triggers.remove(&trigger_name);
4263                                }
4264                            }
4265                            if !dependent_triggers.is_empty() {
4266                                self.snapshot_graph_full();
4267                                self.local.graph.edges.retain(|edge| {
4268                                    !dependent_triggers
4269                                        .iter()
4270                                        .any(|(trigger_id, _)| edge.dependent == *trigger_id)
4271                                });
4272                            }
4273                        }
4274                    }
4275                }
4276                if any_applied {
4277                    MutationResult::Applied
4278                } else {
4279                    MutationResult::Skipped
4280                }
4281            }
4282            Mutation::CreateProcedure(p) => {
4283                if matches!(
4284                    self.local.functions.get(&p.id),
4285                    Some(crate::model::function::FunctionOverlay::Present(_))
4286                ) && !p.or_replace
4287                {
4288                    return MutationResult::Conflict {
4289                        reason: format!("routine '{}' already exists", p.id),
4290                    };
4291                }
4292                self.snapshot_function(&p.id);
4293                self.snapshot_generation_counter();
4294                self.local.generation_counter += 1;
4295                let _generation = self.local.generation_counter;
4296
4297                self.local.functions.insert(
4298                    p.id.clone(),
4299                    crate::model::function::FunctionOverlay::Present(
4300                        crate::model::function::FunctionState {
4301                            id: p.id.clone(),
4302                            arg_types: p.params.iter().map(|p| p.ty.clone()).collect(),
4303                            arg_type_ids: p
4304                                .params
4305                                .iter()
4306                                .map(|parameter| self.resolve_type_reference(&parameter.ty))
4307                                .collect(),
4308                            return_type: "void".to_string(),
4309                            return_type_id: None,
4310                            volatility: crate::model::function::Volatility::Volatile,
4311                            language: "sql".to_string(),
4312                            security: crate::model::function::SecurityMode::Invoker,
4313                        },
4314                    ),
4315                );
4316                MutationResult::Applied
4317            }
4318            Mutation::AlterProcedure(p) => {
4319                self.snapshot_function(&p.id);
4320                // No generation tracking in FunctionState
4321                MutationResult::Applied
4322            }
4323            Mutation::DropProcedure(p) => {
4324                let mut any_applied = false;
4325                for sig in &p.signatures {
4326                    let sig_str = format!("{}({})", sig.name.name.resolve(), sig.params.join(","));
4327                    let schema = self.resolve_function_schema(&sig.name, &sig_str);
4328                    let id = ObjectId::new(schema, sig_str);
4329                    if !matches!(
4330                        self.local.functions.get(&id),
4331                        Some(crate::model::function::FunctionOverlay::Present(_))
4332                    ) {
4333                        if !p.if_exists {
4334                            return MutationResult::Conflict {
4335                                reason: format!("procedure '{}' does not exist", id),
4336                            };
4337                        }
4338                    } else {
4339                        any_applied = true;
4340                        self.snapshot_function(&id);
4341                        self.local
4342                            .functions
4343                            .insert(id, crate::model::function::FunctionOverlay::Dropped);
4344                    }
4345                }
4346                if any_applied {
4347                    MutationResult::Applied
4348                } else {
4349                    MutationResult::Skipped
4350                }
4351            }
4352            Mutation::CreatePublication(p) => {
4353                self.snapshot_publication(&p.name);
4354                self.snapshot_generation_counter();
4355                self.local.generation_counter += 1;
4356                let generation = self.local.generation_counter;
4357
4358                self.local.publications.insert(
4359                    p.name.clone(),
4360                    crate::model::replication::PublicationOverlay::Present(
4361                        crate::model::replication::PublicationState {
4362                            name: p.name.clone(),
4363                            scope: p.scope.clone(),
4364                            params: p.params.clone(),
4365                            generation,
4366                        },
4367                    ),
4368                );
4369
4370                if let crate::analysis::facts::PublicationScope::Explicit(objects) = &p.scope {
4371                    self.snapshot_graph_full();
4372                    for obj in objects {
4373                        if let crate::analysis::facts::PublicationObjectFact::Table {
4374                            name, ..
4375                        } = obj
4376                        {
4377                            let table_id = self.resolve_relation_id(name);
4378                            self.local.graph.edges.push(DependencyEdge::new(
4379                                table_id,
4380                                ObjectId::new("public", &p.name),
4381                                DependencyKind::PublicationIncludes {
4382                                    publication_name: p.name.clone(),
4383                                },
4384                            ));
4385                        }
4386                    }
4387                }
4388                MutationResult::Applied
4389            }
4390            Mutation::AlterPublication(p) => {
4391                self.snapshot_publication(&p.name);
4392                if !self.local.publications.contains_key(&p.name) {
4393                    self.local.confidence = Confidence::Tainted;
4394                    return MutationResult::Skipped;
4395                }
4396                self.snapshot_generation_counter();
4397                self.local.generation_counter += 1;
4398                let new_gen = self.local.generation_counter;
4399
4400                if let Some(crate::model::replication::PublicationOverlay::Present(publ)) =
4401                    self.local.publications.get_mut(&p.name)
4402                {
4403                    publ.generation = new_gen;
4404                }
4405                MutationResult::Applied
4406            }
4407            Mutation::DropPublication(p) => {
4408                for name in &p.names {
4409                    self.snapshot_publication(name);
4410                    if !p.if_exists && !self.local.publications.contains_key(name) {
4411                        self.local.confidence = Confidence::Tainted;
4412                        return MutationResult::Skipped;
4413                    }
4414                    self.local.publications.insert(
4415                        name.clone(),
4416                        crate::model::replication::PublicationOverlay::Dropped,
4417                    );
4418                }
4419                self.snapshot_graph_full();
4420                self.local.graph.edges.retain(|e| {
4421                    !(matches!(e.kind, DependencyKind::PublicationIncludes { .. })
4422                        && p.names.contains(&e.referenced.name))
4423                });
4424                MutationResult::Applied
4425            }
4426            Mutation::CreateSubscription(s) => {
4427                let name = s.name.clone().unwrap_or_else(|| "unnamed_sub".into());
4428                self.snapshot_subscription(&name);
4429                self.snapshot_generation_counter();
4430                self.local.generation_counter += 1;
4431                let generation = self.local.generation_counter;
4432
4433                self.local.subscriptions.insert(
4434                    name.clone(),
4435                    crate::model::replication::SubscriptionOverlay::Present(
4436                        crate::model::replication::SubscriptionState {
4437                            name,
4438                            connection: s.connection.clone(),
4439                            publications: s.publications.clone(),
4440                            params: s.params.clone(),
4441                            generation,
4442                        },
4443                    ),
4444                );
4445                MutationResult::Applied
4446            }
4447            Mutation::AlterSubscription(s) => {
4448                self.snapshot_subscription(&s.name);
4449                if !self.local.subscriptions.contains_key(&s.name) {
4450                    self.local.confidence = Confidence::Tainted;
4451                    return MutationResult::Skipped;
4452                }
4453                self.snapshot_generation_counter();
4454                self.local.generation_counter += 1;
4455                let new_gen = self.local.generation_counter;
4456
4457                if let Some(crate::model::replication::SubscriptionOverlay::Present(sub)) =
4458                    self.local.subscriptions.get_mut(&s.name)
4459                {
4460                    sub.generation = new_gen;
4461                }
4462                MutationResult::Applied
4463            }
4464            Mutation::DropSubscription(s) => {
4465                self.snapshot_subscription(&s.name);
4466                if !s.if_exists && !self.local.subscriptions.contains_key(&s.name) {
4467                    self.local.confidence = Confidence::Tainted;
4468                    return MutationResult::Skipped;
4469                }
4470                self.local.subscriptions.insert(
4471                    s.name.clone(),
4472                    crate::model::replication::SubscriptionOverlay::Dropped,
4473                );
4474                MutationResult::Applied
4475            }
4476            Mutation::CreateRole(r) => {
4477                let role_id = ObjectId::new("", &r.name);
4478                if matches!(
4479                    self.local.roles.get(&role_id),
4480                    Some(crate::model::role::RoleOverlay::Present(_))
4481                ) {
4482                    return MutationResult::Conflict {
4483                        reason: format!("role '{}' already exists", r.name),
4484                    };
4485                }
4486                self.snapshot_role(&role_id);
4487                self.snapshot_generation_counter();
4488                self.local.generation_counter += 1;
4489                let _generation = self.local.generation_counter;
4490
4491                self.local.roles.insert(
4492                    role_id.clone(),
4493                    crate::model::role::RoleOverlay::Present(crate::model::role::RoleState {
4494                        id: role_id,
4495                        can_login: true,
4496                        is_superuser: false,
4497                        member_of: Vec::new(),
4498                        can_set_role_to: Vec::new(),
4499                        granted_privileges: Vec::new(),
4500                    }),
4501                );
4502                MutationResult::Applied
4503            }
4504            Mutation::AlterRole(r) => {
4505                if let Some(role_id) = Self::resolve_role_name(
4506                    &r.name,
4507                    &self.local.current_role,
4508                    &self.local.session_role,
4509                ) {
4510                    self.snapshot_role(&role_id);
4511                    if !self.local.roles.contains_key(&role_id) {
4512                        self.local.confidence = Confidence::Tainted;
4513                        return MutationResult::Skipped;
4514                    }
4515                    self.snapshot_generation_counter();
4516                    self.local.generation_counter += 1;
4517                    let _new_gen = self.local.generation_counter;
4518
4519                    if let Some(crate::model::role::RoleOverlay::Present(_role)) =
4520                        self.local.roles.get_mut(&role_id)
4521                    {
4522                        // No further action as fields have been simplified
4523                    }
4524                    MutationResult::Applied
4525                } else {
4526                    MutationResult::Skipped
4527                }
4528            }
4529            Mutation::DropRole(r) => {
4530                for name in &r.names {
4531                    if let Some(role_id) = Self::resolve_role_name(
4532                        &crate::analysis::facts::RoleFact::Named {
4533                            name: name.clone(),
4534                            via_legacy_group_syntax: false,
4535                        },
4536                        &self.local.current_role,
4537                        &self.local.session_role,
4538                    ) {
4539                        self.snapshot_role(&role_id);
4540                        if !r.if_exists
4541                            && !matches!(
4542                                self.local.roles.get(&role_id),
4543                                Some(crate::model::role::RoleOverlay::Present(_))
4544                            )
4545                        {
4546                            return MutationResult::Conflict {
4547                                reason: format!("role '{}' does not exist", name),
4548                            };
4549                        }
4550                        self.local
4551                            .roles
4552                            .insert(role_id, crate::model::role::RoleOverlay::Dropped);
4553                    }
4554                }
4555                MutationResult::Applied
4556            }
4557            Mutation::Grant(grant) => {
4558                let privileges = Self::resolve_grant_privileges(&grant.privileges);
4559                let grantees = &grant.grantees;
4560                match &grant.target {
4561                    crate::analysis::mutations::ResolvedGrantTarget::Tables(ids) => {
4562                        for id in ids {
4563                            self.apply_grant_to_relation(id, &privileges, grantees);
4564                        }
4565                    }
4566                    crate::analysis::mutations::ResolvedGrantTarget::AllTablesInSchema(schemas) => {
4567                        let target_ids: Vec<ObjectId> = self
4568                            .local
4569                            .relations
4570                            .keys()
4571                            .filter(|id| schemas.contains(&id.schema))
4572                            .cloned()
4573                            .collect();
4574                        for id in &target_ids {
4575                            self.apply_grant_to_relation(id, &privileges, grantees);
4576                        }
4577                    }
4578                }
4579                MutationResult::Applied
4580            }
4581            Mutation::Revoke(revoke) => {
4582                let privileges = Self::resolve_grant_privileges(&revoke.privileges);
4583                let revokees = &revoke.revokees;
4584                match &revoke.target {
4585                    crate::analysis::mutations::ResolvedGrantTarget::Tables(ids) => {
4586                        for id in ids {
4587                            self.apply_revoke_to_relation(id, &privileges, revokees);
4588                        }
4589                    }
4590                    crate::analysis::mutations::ResolvedGrantTarget::AllTablesInSchema(schemas) => {
4591                        let target_ids: Vec<ObjectId> = self
4592                            .local
4593                            .relations
4594                            .keys()
4595                            .filter(|id| schemas.contains(&id.schema))
4596                            .cloned()
4597                            .collect();
4598                        for id in &target_ids {
4599                            self.apply_revoke_to_relation(id, &privileges, revokees);
4600                        }
4601                    }
4602                }
4603                MutationResult::Applied
4604            }
4605            Mutation::CreateDatabase(_) => MutationResult::Applied,
4606            Mutation::AlterDatabase(_) => MutationResult::Applied,
4607            Mutation::DropDatabase(_) => MutationResult::Applied,
4608            Mutation::Vacuum { .. } => MutationResult::Applied,
4609        }
4610    }
4611
4612    fn snapshot_relation(&mut self, id: &ObjectId) {
4613        if let Some(frame) = self.local.transactions.last_mut() {
4614            let previous = self.local.relations.get(id).cloned();
4615            frame.undo_log.push(StateChange::RelationSnapshot {
4616                id: id.clone(),
4617                previous: Box::new(previous),
4618            });
4619        }
4620    }
4621
4622    fn snapshot_schema(&mut self, name: &str) {
4623        if let Some(frame) = self.local.transactions.last_mut() {
4624            frame.undo_log.push(StateChange::SchemaSnapshot {
4625                name: name.to_string(),
4626                previous: self.local.schemas.get(name).cloned(),
4627            });
4628        }
4629    }
4630
4631    fn snapshot_namespace(&mut self) {
4632        if let Some(frame) = self.local.transactions.last_mut() {
4633            frame.undo_log.push(StateChange::NamespaceSnapshot(Box::new(
4634                NamespaceSnapshot {
4635                    schemas: self.local.schemas.clone(),
4636                    relations: self.local.relations.clone(),
4637                    types: self.local.types.clone(),
4638                    functions: self.local.functions.clone(),
4639                    sequences: self.local.sequences.clone(),
4640                    publications: self.local.publications.clone(),
4641                    triggers: self.local.triggers.clone(),
4642                    constraints: self.local.constraints.clone(),
4643                    graph: self.local.graph.edges.clone(),
4644                    pending_validation: self.local.pending_validation.clone(),
4645                    baseline_relations: self.baseline_relations.clone(),
4646                    baseline_indexes: self.baseline_indexes.clone(),
4647                    baseline_foreign_keys: self.baseline_foreign_keys.clone(),
4648                    baseline_fk_dependencies: self.baseline_fk_dependencies.clone(),
4649                    baseline_sequences: self.baseline_sequences.clone(),
4650                },
4651            )));
4652        }
4653    }
4654
4655    fn snapshot_type(&mut self, id: &ObjectId) {
4656        if let Some(frame) = self.local.transactions.last_mut() {
4657            let previous = self.local.types.get(id).cloned();
4658            frame.undo_log.push(StateChange::TypeSnapshot {
4659                id: id.clone(),
4660                previous,
4661            });
4662        }
4663    }
4664
4665    fn snapshot_sequence(&mut self, id: &ObjectId) {
4666        if let Some(frame) = self.local.transactions.last_mut() {
4667            let previous = self.local.sequences.get(id).cloned();
4668            frame.undo_log.push(StateChange::SequenceSnapshot {
4669                id: id.clone(),
4670                previous,
4671            });
4672        }
4673    }
4674
4675    fn move_function(&mut self, old_id: &ObjectId, new_id: &ObjectId) {
4676        self.snapshot_function(old_id);
4677        self.snapshot_function(new_id);
4678        if let Some(crate::model::function::FunctionOverlay::Present(mut function)) =
4679            self.local.functions.remove(old_id)
4680        {
4681            function.id = new_id.clone();
4682            self.local.functions.insert(
4683                new_id.clone(),
4684                crate::model::function::FunctionOverlay::Present(function),
4685            );
4686        }
4687
4688        self.snapshot_graph_full();
4689        self.local.graph.propagate_rename(old_id, new_id);
4690        self.local.graph.edges.push(DependencyEdge::new(
4691            old_id.clone(),
4692            new_id.clone(),
4693            DependencyKind::RenameTo,
4694        ));
4695    }
4696
4697    fn snapshot_function(&mut self, id: &ObjectId) {
4698        if let Some(frame) = self.local.transactions.last_mut() {
4699            let previous = self.local.functions.get(id).cloned();
4700            frame.undo_log.push(StateChange::FunctionSnapshot {
4701                id: id.clone(),
4702                previous,
4703            });
4704        }
4705    }
4706
4707    fn snapshot_publication(&mut self, name: &str) {
4708        if let Some(frame) = self.local.transactions.last_mut() {
4709            let previous = self.local.publications.get(name).cloned();
4710            frame.undo_log.push(StateChange::PublicationSnapshot {
4711                id: ObjectId::new("", name),
4712                previous,
4713            });
4714        }
4715    }
4716
4717    fn snapshot_subscription(&mut self, name: &str) {
4718        if let Some(frame) = self.local.transactions.last_mut() {
4719            let previous = self.local.subscriptions.get(name).cloned();
4720            frame.undo_log.push(StateChange::SubscriptionSnapshot {
4721                id: ObjectId::new("", name),
4722                previous,
4723            });
4724        }
4725    }
4726
4727    fn snapshot_role(&mut self, id: &ObjectId) {
4728        if let Some(frame) = self.local.transactions.last_mut() {
4729            let previous = self.local.roles.get(id).cloned();
4730            frame.undo_log.push(StateChange::RoleSnapshot {
4731                id: id.clone(),
4732                previous,
4733            });
4734        }
4735    }
4736
4737    fn snapshot_trigger(&mut self, id: &ObjectId) {
4738        if let Some(frame) = self.local.transactions.last_mut() {
4739            let previous = self.local.triggers.get(id).cloned();
4740            frame.undo_log.push(StateChange::TriggerSnapshot {
4741                id: id.clone(),
4742                previous,
4743            });
4744        }
4745    }
4746
4747    fn snapshot_constraint(&mut self, table_id: &ObjectId, name: &str) {
4748        if let Some(frame) = self.local.transactions.last_mut() {
4749            let key = (table_id.clone(), name.to_string());
4750            let previous = self.local.constraints.get(&key).cloned();
4751            frame.undo_log.push(StateChange::ConstraintSnapshot {
4752                table_id: table_id.clone(),
4753                name: name.to_string(),
4754                previous,
4755            });
4756        }
4757    }
4758
4759    fn snapshot_role_context(&mut self) {
4760        if let Some(frame) = self.local.transactions.last_mut() {
4761            frame.undo_log.push(StateChange::RoleContextSnapshot {
4762                current_role: self.local.current_role.clone(),
4763                current_role_known: self.local.current_role_known,
4764                persistent_current_role: self.local.persistent_current_role.clone(),
4765                persistent_current_role_known: self.local.persistent_current_role_known,
4766                session_role: self.local.session_role.clone(),
4767                session_role_known: self.local.session_role_known,
4768                persistent_session_role: self.local.persistent_session_role.clone(),
4769                persistent_session_role_known: self.local.persistent_session_role_known,
4770            });
4771        }
4772    }
4773
4774    fn snapshot_search_path(&mut self) {
4775        if let Some(frame) = self.local.transactions.last_mut() {
4776            frame.undo_log.push(StateChange::SearchPathSnapshot {
4777                previous: self.local.search_path.clone(),
4778                previous_template: self.local.search_path_template.clone(),
4779            });
4780        }
4781    }
4782
4783    fn snapshot_generation_counter(&mut self) {
4784        if let Some(frame) = self.local.transactions.last_mut() {
4785            frame.undo_log.push(StateChange::GenerationCounterSnapshot {
4786                previous: self.local.generation_counter,
4787            });
4788        }
4789    }
4790
4791    #[allow(dead_code)]
4792    fn snapshot_pending_validation(&mut self) {
4793        if let Some(frame) = self.local.transactions.last_mut() {
4794            frame.undo_log.push(StateChange::PendingValidationSnapshot {
4795                previous: self.local.pending_validation.clone(),
4796            });
4797        }
4798    }
4799
4800    fn snapshot_confidence(&mut self) {
4801        if let Some(frame) = self.local.transactions.last_mut() {
4802            frame.undo_log.push(StateChange::ConfidenceSnapshot {
4803                previous: self.local.confidence.clone(),
4804            });
4805        }
4806    }
4807
4808    fn snapshot_graph(&mut self) {
4809        if let Some(frame) = self.local.transactions.last_mut() {
4810            frame.undo_log.push(StateChange::GraphLengthMarker {
4811                len: self.local.graph.edges.len(),
4812            });
4813        }
4814    }
4815
4816    fn snapshot_graph_full(&mut self) {
4817        if let Some(frame) = self.local.transactions.last_mut() {
4818            frame.undo_log.push(StateChange::GraphSnapshot {
4819                previous: self.local.graph.edges.clone(),
4820            });
4821        }
4822    }
4823
4824    fn rollback_frame(&mut self, mut frame: TransactionFrame) {
4825        self.rollback_undo_log(std::mem::take(&mut frame.undo_log));
4826    }
4827
4828    fn rollback_undo_log(&mut self, mut undo_log: Vec<StateChange>) {
4829        while let Some(change) = undo_log.pop() {
4830            match change {
4831                StateChange::SchemaSnapshot { name, previous } => match previous {
4832                    Some(overlay) => {
4833                        self.local.schemas.insert(name, overlay);
4834                    }
4835                    None => {
4836                        self.local.schemas.remove(&name);
4837                    }
4838                },
4839                StateChange::NamespaceSnapshot(snapshot) => {
4840                    self.local.schemas = snapshot.schemas;
4841                    self.local.relations = snapshot.relations;
4842                    self.local.types = snapshot.types;
4843                    self.local.functions = snapshot.functions;
4844                    self.local.sequences = snapshot.sequences;
4845                    self.local.publications = snapshot.publications;
4846                    self.local.triggers = snapshot.triggers;
4847                    self.local.constraints = snapshot.constraints;
4848                    self.local.graph.edges = snapshot.graph;
4849                    self.local.pending_validation = snapshot.pending_validation;
4850                    self.baseline_relations = snapshot.baseline_relations;
4851                    self.baseline_indexes = snapshot.baseline_indexes;
4852                    self.baseline_foreign_keys = snapshot.baseline_foreign_keys;
4853                    self.baseline_fk_dependencies = snapshot.baseline_fk_dependencies;
4854                    self.baseline_sequences = snapshot.baseline_sequences;
4855                }
4856                StateChange::RelationSnapshot { id, previous } => {
4857                    if let Some(prev) = *previous {
4858                        self.local.relations.insert(id, prev);
4859                    } else {
4860                        self.local.relations.remove(&id);
4861                    }
4862                }
4863                StateChange::TypeSnapshot { id, previous } => {
4864                    if let Some(prev) = previous {
4865                        self.local.types.insert(id, prev);
4866                    } else {
4867                        self.local.types.remove(&id);
4868                    }
4869                }
4870                StateChange::SequenceSnapshot { id, previous } => {
4871                    if let Some(prev) = previous {
4872                        self.local.sequences.insert(id, prev);
4873                    } else {
4874                        self.local.sequences.remove(&id);
4875                    }
4876                }
4877                StateChange::FunctionSnapshot { id, previous } => {
4878                    if let Some(prev) = previous {
4879                        self.local.functions.insert(id, prev);
4880                    } else {
4881                        self.local.functions.remove(&id);
4882                    }
4883                }
4884                StateChange::PublicationSnapshot { id, previous } => {
4885                    if let Some(prev) = previous {
4886                        self.local.publications.insert(id.name, prev);
4887                    } else {
4888                        self.local.publications.remove(&id.name);
4889                    }
4890                }
4891                StateChange::SubscriptionSnapshot { id, previous } => {
4892                    if let Some(prev) = previous {
4893                        self.local.subscriptions.insert(id.name, prev);
4894                    } else {
4895                        self.local.subscriptions.remove(&id.name);
4896                    }
4897                }
4898                StateChange::RoleSnapshot { id, previous } => {
4899                    if let Some(prev) = previous {
4900                        self.local.roles.insert(id, prev);
4901                    } else {
4902                        self.local.roles.remove(&id);
4903                    }
4904                }
4905                StateChange::TriggerSnapshot { id, previous } => {
4906                    if let Some(prev) = previous {
4907                        self.local.triggers.insert(id, prev);
4908                    } else {
4909                        self.local.triggers.remove(&id);
4910                    }
4911                }
4912                StateChange::ConstraintSnapshot {
4913                    table_id,
4914                    name,
4915                    previous,
4916                } => {
4917                    let key = (table_id, name);
4918                    if let Some(previous) = previous {
4919                        self.local.constraints.insert(key, previous);
4920                    } else {
4921                        self.local.constraints.remove(&key);
4922                    }
4923                }
4924                StateChange::GraphLengthMarker { len } => {
4925                    self.local.graph.edges.truncate(len);
4926                }
4927                StateChange::GraphSnapshot { previous } => {
4928                    self.local.graph.edges = previous;
4929                }
4930                StateChange::RoleContextSnapshot {
4931                    current_role,
4932                    current_role_known,
4933                    persistent_current_role,
4934                    persistent_current_role_known,
4935                    session_role,
4936                    session_role_known,
4937                    persistent_session_role,
4938                    persistent_session_role_known,
4939                } => {
4940                    self.local.current_role = current_role;
4941                    self.local.current_role_known = current_role_known;
4942                    self.local.persistent_current_role = persistent_current_role;
4943                    self.local.persistent_current_role_known = persistent_current_role_known;
4944                    self.local.session_role = session_role;
4945                    self.local.session_role_known = session_role_known;
4946                    self.local.persistent_session_role = persistent_session_role;
4947                    self.local.persistent_session_role_known = persistent_session_role_known;
4948                }
4949                StateChange::SearchPathSnapshot {
4950                    previous,
4951                    previous_template,
4952                } => {
4953                    self.local.search_path = previous;
4954                    self.local.search_path_template = previous_template;
4955                }
4956                StateChange::GenerationCounterSnapshot { previous } => {
4957                    self.local.generation_counter = previous;
4958                }
4959                StateChange::PendingValidationSnapshot { previous } => {
4960                    self.local.pending_validation = previous;
4961                }
4962                StateChange::ConfidenceSnapshot { previous } => {
4963                    self.local.confidence = previous;
4964                }
4965            }
4966        }
4967    }
4968}