Skip to main content

safe_migrate/analysis/
resolver.rs

1use crate::analysis::facts::{
2    AlterIndexActionFact, AlterTableActionFact, PersistenceFact, StatementFact, TypeCreationKind,
3};
4use crate::analysis::mutations::{
5    AlterAggregateMutation, AlterDatabaseMutation, AlterDomainMutation, AlterFunctionMutation,
6    AlterProcedureMutation, AlterPublicationMutation, AlterRoleMutation, AlterSchemaMutation,
7    AlterSequenceActionMutation, AlterSequenceMutation, AlterSubscriptionMutation, AlterTable,
8    AlterTableActionMutation, AlterTypeActionMutation, AlterTypeMutation, ColumnMutation,
9    CreateAggregateMutation, CreateDatabaseMutation, CreateDomainMutation, CreateFunctionMutation,
10    CreateIndex, CreateMaterializedView, CreatePolicyMutation, CreateProcedureMutation,
11    CreatePublicationMutation, CreateRoleMutation, CreateSchemaMutation, CreateSequenceMutation,
12    CreateSubscriptionMutation, CreateTable, CreateTriggerMutation, CreateTypeMutation, CreateView,
13    DropAggregateMutation, DropDatabaseMutation, DropDomainMutation, DropFunctionMutation,
14    DropIndex, DropMaterializedViewMutation, DropPolicyMutation, DropProcedureMutation,
15    DropPublicationMutation, DropRoleMutation, DropSchemaMutation, DropSequenceMutation,
16    DropSubscriptionMutation, DropTable, DropTriggerMutation, DropTypeMutation, DropViewMutation,
17    FkMutation, GrantMutation, Mutation, OpaqueMutation, PersistenceMutation,
18    RefreshMaterializedViewMutation, ReleaseSavepointMutation, Rename, RenameTriggerMutation,
19    ResolvedGrantTarget, RevokeMutation, RollbackToSavepointMutation, SavepointMutation,
20    SearchPathChange, TimeoutSettingChange,
21};
22use crate::analysis::state::AnalysisState;
23use crate::ast::identifiers::{ObjectId, QualifiedName};
24use crate::model::types::TypeKind;
25
26pub struct Resolver;
27
28impl Resolver {
29    fn resolve_creation_name(name: &QualifiedName, state: &AnalysisState) -> ObjectId {
30        let schema = name
31            .schema
32            .as_ref()
33            .map(|i| i.resolve())
34            .unwrap_or_else(|| {
35                state
36                    .local
37                    .search_path
38                    .first()
39                    .map(|s| s.as_str())
40                    .unwrap_or("public")
41                    .to_string()
42            });
43
44        ObjectId::new(schema, name.name.resolve())
45    }
46
47    fn resolve_lookup_name(name: &QualifiedName, state: &AnalysisState) -> ObjectId {
48        if let Some(schema_ident) = &name.schema {
49            return ObjectId::new(schema_ident.resolve(), name.name.resolve());
50        }
51
52        let resolved_name = name.name.resolve();
53
54        for schema in &state.local.search_path {
55            let mut candidate = ObjectId::new(schema.clone(), resolved_name.clone());
56            if state.local.relations.contains_key(&candidate)
57                || state.local.types.contains_key(&candidate)
58                || state.local.sequences.contains_key(&candidate)
59                || state.local.functions.keys().any(|k| {
60                    k.schema == candidate.schema
61                        && (k.name == candidate.name
62                            || k.name.starts_with(&format!("{}(", candidate.name)))
63                })
64            {
65                candidate.inferred_schema = true;
66                return candidate;
67            }
68        }
69
70        let schema = state
71            .local
72            .search_path
73            .first()
74            .map(|s| s.as_str())
75            .unwrap_or("public")
76            .to_string();
77        let mut id = ObjectId::new(schema, resolved_name);
78        id.inferred_schema = true;
79        id
80    }
81
82    fn resolve_type_lookup_name(name: &QualifiedName, state: &AnalysisState) -> ObjectId {
83        if let Some(schema_ident) = &name.schema {
84            return ObjectId::new(schema_ident.resolve(), name.name.resolve());
85        }
86
87        let resolved_name = name.name.resolve();
88        for schema in &state.local.search_path {
89            let mut candidate = ObjectId::new(schema.clone(), resolved_name.clone());
90            if matches!(
91                state.local.types.get(&candidate),
92                Some(crate::model::types::TypeOverlay::Present(_))
93            ) {
94                candidate.inferred_schema = true;
95                return candidate;
96            }
97        }
98
99        let schema = state
100            .local
101            .search_path
102            .first()
103            .cloned()
104            .unwrap_or_else(|| "public".to_string());
105        let mut id = ObjectId::new(schema, resolved_name);
106        id.inferred_schema = true;
107        id
108    }
109
110    fn resolve_constraint_index_name(name: &QualifiedName, table: &ObjectId) -> ObjectId {
111        let schema = name
112            .schema
113            .as_ref()
114            .map(|schema| schema.resolve())
115            .unwrap_or_else(|| table.schema.clone());
116        ObjectId::new(schema, name.name.resolve())
117    }
118
119    fn resolve_function_id(
120        name: &QualifiedName,
121        params: &[crate::analysis::facts::ParamFact],
122        state: &AnalysisState,
123    ) -> ObjectId {
124        let base_id = Self::resolve_creation_name(name, state);
125        let sig = params
126            .iter()
127            .filter(|p| !matches!(&p.mode, crate::analysis::facts::ParamModeFact::Out))
128            .map(|p| p.ty.clone())
129            .collect::<Vec<_>>()
130            .join(",");
131        Self::resolve_function_id_by_sig(&base_id, &sig)
132    }
133
134    fn resolve_function_id_by_sig(base_id: &ObjectId, sig: &str) -> ObjectId {
135        // Normalize types in signature to match pg_proc standard names
136        let normalized_sig = sig
137            .split(',')
138            .map(Self::normalize_function_arg_type)
139            .collect::<Vec<_>>()
140            .join(",");
141
142        let mut id = ObjectId::new(
143            base_id.schema.clone(),
144            format!("{}({})", base_id.name, normalized_sig),
145        );
146        id.inferred_schema = base_id.inferred_schema;
147        id
148    }
149
150    fn resolve_publication_object(
151        object: &crate::analysis::facts::PublicationObjectFact,
152        state: &AnalysisState,
153    ) -> crate::analysis::facts::PublicationObjectFact {
154        match object {
155            crate::analysis::facts::PublicationObjectFact::Table {
156                name,
157                only,
158                include_partitions,
159                columns,
160                row_filter,
161            } => {
162                let id = Self::resolve_lookup_name(name, state);
163                crate::analysis::facts::PublicationObjectFact::Table {
164                    name: crate::ast::identifiers::QualifiedName::new(
165                        Some(crate::ast::identifiers::Ident::new(id.schema, true)),
166                        crate::ast::identifiers::Ident::new(id.name, true),
167                    ),
168                    only: *only,
169                    include_partitions: *include_partitions,
170                    columns: columns.clone(),
171                    row_filter: row_filter.clone(),
172                }
173            }
174            crate::analysis::facts::PublicationObjectFact::CurrentSchemaShorthand => {
175                crate::analysis::facts::PublicationObjectFact::SchemaTables {
176                    schema: state
177                        .local
178                        .search_path
179                        .first()
180                        .cloned()
181                        .unwrap_or_else(|| "public".to_string()),
182                    row_filter: None,
183                }
184            }
185            other => other.clone(),
186        }
187    }
188
189    fn resolve_publication_scope(
190        scope: &crate::analysis::facts::PublicationScope,
191        state: &AnalysisState,
192    ) -> crate::analysis::facts::PublicationScope {
193        match scope {
194            crate::analysis::facts::PublicationScope::AllTables { except } => {
195                crate::analysis::facts::PublicationScope::AllTables {
196                    except: except.clone(),
197                }
198            }
199            crate::analysis::facts::PublicationScope::Explicit(objects) => {
200                crate::analysis::facts::PublicationScope::Explicit(
201                    objects
202                        .iter()
203                        .map(|object| Self::resolve_publication_object(object, state))
204                        .collect(),
205                )
206            }
207        }
208    }
209
210    fn resolve_alter_publication_action(
211        action: &crate::analysis::facts::AlterPublicationActionFact,
212        state: &AnalysisState,
213    ) -> crate::analysis::facts::AlterPublicationActionFact {
214        use crate::analysis::facts::AlterPublicationActionFact;
215        match action {
216            AlterPublicationActionFact::AddObjects(objects) => {
217                AlterPublicationActionFact::AddObjects(
218                    objects
219                        .iter()
220                        .map(|object| Self::resolve_publication_object(object, state))
221                        .collect(),
222                )
223            }
224            AlterPublicationActionFact::DropObjects(objects) => {
225                AlterPublicationActionFact::DropObjects(
226                    objects
227                        .iter()
228                        .map(|object| Self::resolve_publication_object(object, state))
229                        .collect(),
230                )
231            }
232            AlterPublicationActionFact::SetObjects(scope) => {
233                AlterPublicationActionFact::SetObjects(Self::resolve_publication_scope(
234                    scope, state,
235                ))
236            }
237            other => other.clone(),
238        }
239    }
240
241    pub(crate) fn normalize_function_arg_type(raw: &str) -> String {
242        let normalized = Self::fold_unquoted_identifier_case(raw.trim());
243        if let Some(element_type) = normalized.strip_suffix("[]") {
244            return format!("{}[]", Self::normalize_function_arg_type(element_type));
245        }
246        match normalized.as_str() {
247            "int" | "int4" => "integer".to_string(),
248            "int8" => "bigint".to_string(),
249            "int2" => "smallint".to_string(),
250            "float8" => "double precision".to_string(),
251            "float4" => "real".to_string(),
252            "bool" => "boolean".to_string(),
253            "varchar" => "character varying".to_string(),
254            "char" => "character".to_string(),
255            "time" => "time without time zone".to_string(),
256            "timestamp" => "timestamp without time zone".to_string(),
257            "timestamptz" => "timestamp with time zone".to_string(),
258            "decimal" => "numeric".to_string(),
259            _ => normalized,
260        }
261    }
262
263    fn fold_unquoted_identifier_case(raw: &str) -> String {
264        let mut folded = String::with_capacity(raw.len());
265        let mut quoted = false;
266        let mut chars = raw.chars().peekable();
267        while let Some(character) = chars.next() {
268            match character {
269                '"' if quoted && chars.peek() == Some(&'"') => {
270                    folded.push('"');
271                    folded.push('"');
272                    chars.next();
273                }
274                '"' => {
275                    quoted = !quoted;
276                    folded.push(character);
277                }
278                character if quoted => folded.push(character),
279                character => folded.extend(character.to_lowercase()),
280            }
281        }
282        folded
283    }
284
285    fn resolve_grant_target(
286        target: &crate::analysis::facts::GrantTarget,
287        state: &AnalysisState,
288    ) -> ResolvedGrantTarget {
289        match target {
290            crate::analysis::facts::GrantTarget::Tables(names) => ResolvedGrantTarget::Tables(
291                names
292                    .iter()
293                    .map(|n| Self::resolve_lookup_name(n, state))
294                    .collect(),
295            ),
296            crate::analysis::facts::GrantTarget::AllTablesInSchema(schemas) => {
297                ResolvedGrantTarget::AllTablesInSchema(schemas.clone())
298            }
299        }
300    }
301
302    pub fn resolve(fact: &StatementFact, state: &AnalysisState) -> Vec<Mutation> {
303        let mut mutations = Vec::new();
304        match fact {
305            StatementFact::CreateSchema {
306                name,
307                if_not_exists,
308                authorization,
309            } => {
310                mutations.push(Mutation::CreateSchema(CreateSchemaMutation {
311                    name: name.name.resolve(),
312                    if_not_exists: *if_not_exists,
313                    authorization: authorization.clone(),
314                }));
315            }
316            StatementFact::SchemaNeutralNoop => {}
317            StatementFact::AlterSchema { name, action } => {
318                let name = name.name.resolve();
319                let action = match action {
320                    crate::analysis::facts::AlterSchemaActionFact::RenameTo { new_name } => {
321                        AlterSchemaMutation::Rename {
322                            old_name: name,
323                            new_name: new_name.resolve(),
324                        }
325                    }
326                    crate::analysis::facts::AlterSchemaActionFact::OwnerTo { new_owner } => {
327                        AlterSchemaMutation::OwnerTo {
328                            name,
329                            new_owner: new_owner.clone(),
330                        }
331                    }
332                };
333                mutations.push(Mutation::AlterSchema(action));
334            }
335            StatementFact::DropSchema {
336                names,
337                if_exists,
338                cascade,
339            } => {
340                mutations.push(Mutation::DropSchema(DropSchemaMutation {
341                    names: names.iter().map(|n| n.name.resolve()).collect(),
342                    if_exists: *if_exists,
343                    cascade: *cascade,
344                }));
345            }
346            StatementFact::CreateTable {
347                name,
348                if_not_exists,
349                as_select,
350                persistence,
351                columns,
352                foreign_keys,
353                table_constraints,
354                partition_by,
355                partition_of,
356                partition_type,
357            } => {
358                let id = Self::resolve_creation_name(name, state);
359
360                let resolved_persistence = match persistence {
361                    PersistenceFact::Permanent => PersistenceMutation::Permanent,
362                    PersistenceFact::Temporary => PersistenceMutation::Temporary,
363                    PersistenceFact::Unlogged => PersistenceMutation::Unlogged,
364                };
365
366                let col_mutations: Vec<ColumnMutation> = columns
367                    .iter()
368                    .map(|c| ColumnMutation {
369                        name: c.name.clone(),
370                        ty: c.ty.clone(),
371                        not_null: c.not_null,
372                        is_primary_key: c.is_primary_key,
373                        primary_key_constraint_name: c.primary_key_constraint_name.clone(),
374                        is_unique: c.is_unique,
375                        unique_constraint_name: c.unique_constraint_name.clone(),
376                        default: c.default.clone(),
377                        generation: c.generation,
378                    })
379                    .collect();
380
381                let mut fk_mutations = Vec::new();
382                for fk in foreign_keys {
383                    let to_table = Self::resolve_lookup_name(&fk.references, state);
384
385                    fk_mutations.push(FkMutation {
386                        constraint_name: fk.constraint_name.clone(),
387                        to_table,
388                        from_columns: fk.from_columns.clone(),
389                        to_columns: fk.to_columns.clone(),
390                    });
391                }
392
393                let partition_of_id = partition_of
394                    .as_ref()
395                    .map(|n| Self::resolve_lookup_name(n, state));
396
397                mutations.push(Mutation::CreateTable(CreateTable {
398                    id,
399                    if_not_exists: *if_not_exists,
400                    as_select: *as_select,
401                    persistence: resolved_persistence,
402                    columns: col_mutations,
403                    foreign_keys: fk_mutations,
404                    table_constraints: table_constraints.clone(),
405                    partition_by: partition_by.clone(),
406                    partition_of: partition_of_id,
407                    partition_type: partition_type.clone(),
408                }));
409            }
410            StatementFact::CreateView {
411                name,
412                or_replace,
413                depends_on,
414            } => {
415                let id = Self::resolve_creation_name(name, state);
416
417                let resolved_depends = depends_on
418                    .iter()
419                    .map(|n| Self::resolve_lookup_name(n, state))
420                    .collect();
421
422                mutations.push(Mutation::CreateView(CreateView {
423                    id,
424                    or_replace: *or_replace,
425                    depends_on: resolved_depends,
426                }));
427            }
428            StatementFact::AlterView { name, action } => {
429                match action {
430                    crate::analysis::facts::AlterViewAction::RenameTo { new_name } => {
431                        let id = Self::resolve_lookup_name(name, state);
432                        let mut new_id = ObjectId::new(id.schema.clone(), new_name.resolve());
433                        new_id.inferred_schema = id.inferred_schema;
434                        mutations.push(Mutation::Rename(Rename { old_id: id, new_id }));
435                    }
436                    crate::analysis::facts::AlterViewAction::SetSchema { new_schema } => {
437                        let id = Self::resolve_lookup_name(name, state);
438                        let new_id = ObjectId::new(new_schema, &id.name);
439                        mutations.push(Mutation::Rename(Rename { old_id: id, new_id }));
440                    }
441                    crate::analysis::facts::AlterViewAction::OwnerTo { new_owner } => {
442                        mutations.push(Mutation::ChangeRelationOwner {
443                            id: Self::resolve_lookup_name(name, state),
444                            new_owner: new_owner.clone(),
445                        });
446                    }
447                    crate::analysis::facts::AlterViewAction::SetDefault { .. }
448                    | crate::analysis::facts::AlterViewAction::DropDefault { .. }
449                    | crate::analysis::facts::AlterViewAction::RenameColumn { .. }
450                    | crate::analysis::facts::AlterViewAction::SetOptions { .. }
451                    | crate::analysis::facts::AlterViewAction::ResetOptions { .. } => {
452                        // These are opaque from the state machine's perspective —
453                        // they don't create or destroy objects, just modify metadata.
454                        // No mutation emitted; rules can still check the StatementFact.
455                    }
456                }
457            }
458            StatementFact::CreateMaterializedView { name, depends_on } => {
459                let id = Self::resolve_creation_name(name, state);
460
461                let resolved_depends = depends_on
462                    .iter()
463                    .map(|n| Self::resolve_lookup_name(n, state))
464                    .collect();
465
466                mutations.push(Mutation::CreateMaterializedView(CreateMaterializedView {
467                    id,
468                    depends_on: resolved_depends,
469                }));
470            }
471            StatementFact::AlterMaterializedView { name, new_name } => {
472                if let Some(new_name) = new_name {
473                    let id = Self::resolve_lookup_name(name, state);
474                    let mut new_id = ObjectId::new(id.schema.clone(), new_name.resolve());
475                    new_id.inferred_schema = id.inferred_schema;
476                    mutations.push(Mutation::Rename(Rename { old_id: id, new_id }));
477                }
478            }
479            StatementFact::RefreshMaterializedView { name, concurrently } => {
480                mutations.push(Mutation::RefreshMaterializedView(
481                    RefreshMaterializedViewMutation {
482                        id: Self::resolve_lookup_name(name, state),
483                        concurrently: *concurrently,
484                    },
485                ));
486            }
487            StatementFact::CreateIndex {
488                name,
489                relation,
490                if_not_exists,
491                concurrently,
492                using_method,
493                has_predicate,
494                unique,
495            } => {
496                let table = Self::resolve_lookup_name(relation, state);
497                // PostgreSQL places an unqualified index in the indexed
498                // relation's schema, not the first schema in search_path.
499                let id = if name.schema.is_some() {
500                    Self::resolve_creation_name(name, state)
501                } else {
502                    ObjectId::new(table.schema.clone(), name.name.resolve())
503                };
504
505                mutations.push(Mutation::CreateIndex(CreateIndex {
506                    id,
507                    table,
508                    if_not_exists: *if_not_exists,
509                    concurrently: *concurrently,
510                    using_method: using_method.clone(),
511                    has_predicate: *has_predicate,
512                    unique: *unique,
513                }));
514            }
515            StatementFact::CreatePolicy {
516                name,
517                table,
518                permissive,
519                command,
520            } => {
521                mutations.push(Mutation::CreatePolicy(CreatePolicyMutation {
522                    name: name.clone(),
523                    table: Self::resolve_lookup_name(table, state),
524                    permissive: *permissive,
525                    command: command.clone(),
526                }));
527            }
528            StatementFact::DropPolicy {
529                name,
530                table,
531                if_exists,
532            } => {
533                mutations.push(Mutation::DropPolicy(DropPolicyMutation {
534                    name: name.clone(),
535                    table: Self::resolve_lookup_name(table, state),
536                    if_exists: *if_exists,
537                }));
538            }
539            StatementFact::CreateTrigger {
540                name,
541                table,
542                function,
543            } => {
544                // Function references in triggers are bare names (e.g., "notify_func")
545                // but functions are stored with signature (e.g., "notify_func()").
546                // Use resolve_function_id_by_sig with empty params for consistent lookup.
547                let function_base = function
548                    .as_ref()
549                    .map(|f| Self::resolve_lookup_name(f, state))
550                    .unwrap_or_else(|| ObjectId::new("public", "unknown_function"));
551                let function_id = Self::resolve_function_id_by_sig(&function_base, "");
552                mutations.push(Mutation::CreateTrigger(CreateTriggerMutation {
553                    name: name.clone(),
554                    table: Self::resolve_lookup_name(table, state),
555                    function_id,
556                }));
557            }
558            StatementFact::DropTrigger {
559                name,
560                table,
561                if_exists,
562            } => {
563                mutations.push(Mutation::DropTrigger(DropTriggerMutation {
564                    name: name.clone(),
565                    table: Self::resolve_lookup_name(table, state),
566                    if_exists: *if_exists,
567                }));
568            }
569            StatementFact::AlterTrigger {
570                name,
571                table,
572                new_name,
573            } => mutations.push(Mutation::RenameTrigger(RenameTriggerMutation {
574                name: name.clone(),
575                table: Self::resolve_lookup_name(table, state),
576                new_name: new_name.clone(),
577            })),
578            StatementFact::AlterIndex { name, actions } => {
579                let id = Self::resolve_lookup_name(name, state);
580                for action in actions {
581                    match action {
582                        AlterIndexActionFact::RenameTo { new_name } => {
583                            let mut new_id = ObjectId::new(id.schema.clone(), new_name.resolve());
584                            new_id.inferred_schema = id.inferred_schema;
585                            mutations.push(Mutation::Rename(Rename {
586                                old_id: id.clone(),
587                                new_id,
588                            }));
589                        }
590                    }
591                }
592            }
593            StatementFact::CreateType(create_type) => {
594                let id = Self::resolve_creation_name(&create_type.name, state);
595
596                let mapped_kind = match &create_type.kind {
597                    TypeCreationKind::Enum { variants } => TypeKind::Enum {
598                        variants: variants.clone(),
599                    },
600                    TypeCreationKind::Range => TypeKind::Range,
601                    TypeCreationKind::Composite => TypeKind::Composite,
602                    TypeCreationKind::Base => TypeKind::Base,
603                };
604
605                mutations.push(Mutation::CreateType(CreateTypeMutation {
606                    id,
607                    kind: mapped_kind,
608                }));
609            }
610            StatementFact::AlterType(alter_type) => {
611                let id = Self::resolve_type_lookup_name(&alter_type.name, state);
612                for action_fact in &alter_type.actions {
613                    match action_fact {
614                        crate::analysis::facts::AlterTypeActionFact::RenameTo { new_name } => {
615                            let mut new_id = ObjectId::new(id.schema.clone(), new_name.resolve());
616                            new_id.inferred_schema = id.inferred_schema;
617                            mutations.push(Mutation::RenameType(Rename {
618                                old_id: id.clone(),
619                                new_id,
620                            }));
621                        }
622                        crate::analysis::facts::AlterTypeActionFact::SetSchema { new_schema } => {
623                            mutations.push(Mutation::RenameType(Rename {
624                                old_id: id.clone(),
625                                new_id: ObjectId::new(new_schema, &id.name),
626                            }));
627                        }
628                        crate::analysis::facts::AlterTypeActionFact::AddValue {
629                            new_value,
630                            neighbor,
631                            before,
632                        } => {
633                            mutations.push(Mutation::AlterType(AlterTypeMutation {
634                                id: id.clone(),
635                                action: AlterTypeActionMutation::AddValue {
636                                    new_value: new_value.clone(),
637                                    neighbor: neighbor.clone(),
638                                    before: *before,
639                                },
640                            }));
641                        }
642                        crate::analysis::facts::AlterTypeActionFact::RenameValue {
643                            old_value,
644                            new_value,
645                        } => {
646                            mutations.push(Mutation::AlterType(AlterTypeMutation {
647                                id: id.clone(),
648                                action: AlterTypeActionMutation::RenameValue {
649                                    old_value: old_value.clone(),
650                                    new_value: new_value.clone(),
651                                },
652                            }));
653                        }
654                    }
655                }
656            }
657            StatementFact::CreateDomain { name, base_type } => {
658                let id = Self::resolve_creation_name(name, state);
659
660                mutations.push(Mutation::CreateDomain(CreateDomainMutation {
661                    id,
662                    base_type: base_type.clone(),
663                }));
664            }
665            StatementFact::AlterDomain { name, action } => {
666                mutations.push(Mutation::AlterDomain(AlterDomainMutation {
667                    id: Self::resolve_lookup_name(name, state),
668                    action: action.clone(),
669                }));
670            }
671            StatementFact::DropDomain {
672                names,
673                if_exists,
674                cascade,
675            } => {
676                let ids = names
677                    .iter()
678                    .map(|n| Self::resolve_lookup_name(n, state))
679                    .collect();
680                mutations.push(Mutation::DropDomain(DropDomainMutation {
681                    ids,
682                    if_exists: *if_exists,
683                    cascade: *cascade,
684                }));
685            }
686            StatementFact::DropType {
687                names,
688                if_exists,
689                cascade,
690            } => {
691                let ids = names
692                    .iter()
693                    .map(|n| Self::resolve_lookup_name(n, state))
694                    .collect();
695                mutations.push(Mutation::DropType(DropTypeMutation {
696                    ids,
697                    if_exists: *if_exists,
698                    cascade: *cascade,
699                }));
700            }
701            StatementFact::CreateSequence {
702                name,
703                if_not_exists,
704                owned_by,
705            } => {
706                let id = Self::resolve_creation_name(name, state);
707
708                let resolved_owned_by = owned_by.as_ref().map(|(table_name, col)| {
709                    (Self::resolve_lookup_name(table_name, state), col.clone())
710                });
711                mutations.push(Mutation::CreateSequence(CreateSequenceMutation {
712                    id,
713                    if_not_exists: *if_not_exists,
714                    owned_by: resolved_owned_by,
715                }));
716            }
717            StatementFact::AlterSequence {
718                name,
719                if_exists,
720                action,
721            } => {
722                let id = Self::resolve_lookup_name(name, state);
723                let action = match action {
724                    crate::analysis::facts::AlterSequenceActionFact::OwnedBy(owned_by) => {
725                        AlterSequenceActionMutation::OwnedBy(owned_by.as_ref().map(
726                            |(table_name, col)| {
727                                (Self::resolve_lookup_name(table_name, state), col.clone())
728                            },
729                        ))
730                    }
731                    crate::analysis::facts::AlterSequenceActionFact::OwnerTo(owner) => {
732                        AlterSequenceActionMutation::OwnerTo(owner.clone())
733                    }
734                    crate::analysis::facts::AlterSequenceActionFact::RenameTo(new_name) => {
735                        AlterSequenceActionMutation::RenameTo(ObjectId::new(
736                            &id.schema,
737                            new_name.resolve(),
738                        ))
739                    }
740                    crate::analysis::facts::AlterSequenceActionFact::SetSchema(schema) => {
741                        AlterSequenceActionMutation::SetSchema(ObjectId::new(schema, &id.name))
742                    }
743                    crate::analysis::facts::AlterSequenceActionFact::Other => {
744                        AlterSequenceActionMutation::Other
745                    }
746                };
747                mutations.push(Mutation::AlterSequence(AlterSequenceMutation {
748                    id,
749                    if_exists: *if_exists,
750                    action,
751                }));
752            }
753            StatementFact::DropSequence {
754                names,
755                if_exists,
756                cascade,
757            } => {
758                let ids = names
759                    .iter()
760                    .map(|n| Self::resolve_lookup_name(n, state))
761                    .collect();
762                mutations.push(Mutation::DropSequence(DropSequenceMutation {
763                    ids,
764                    if_exists: *if_exists,
765                    cascade: *cascade,
766                }));
767            }
768            StatementFact::AlterTable { name, actions } => {
769                let id = Self::resolve_lookup_name(name, state);
770                for action_fact in actions {
771                    let action = match action_fact {
772                        AlterTableActionFact::AddColumn {
773                            name: col_name,
774                            ty,
775                            if_not_exists,
776                            not_null,
777                            default,
778                            generation,
779                        } => AlterTableActionMutation::AddColumn {
780                            name: col_name.clone(),
781                            ty: ty.clone(),
782                            if_not_exists: *if_not_exists,
783                            not_null: *not_null,
784                            default: default.clone(),
785                            depends_on: None, // Logic for extraction can be added later if needed
786                            generation: *generation,
787                        },
788                        AlterTableActionFact::DropColumn {
789                            name: col_name,
790                            if_exists,
791                        } => AlterTableActionMutation::DropColumn {
792                            name: col_name.clone(),
793                            if_exists: *if_exists,
794                        },
795                        AlterTableActionFact::RenameColumn { from, to } => {
796                            AlterTableActionMutation::RenameColumn {
797                                from: from.resolve(),
798                                to: to.resolve(),
799                            }
800                        }
801                        AlterTableActionFact::RenameTo { new_name } => {
802                            let mut new_id = ObjectId::new(id.schema.clone(), new_name.resolve());
803                            new_id.inferred_schema = id.inferred_schema;
804                            mutations.push(Mutation::Rename(Rename {
805                                old_id: id.clone(),
806                                new_id,
807                            }));
808                            continue;
809                        }
810                        AlterTableActionFact::SetSchema { new_schema } => {
811                            let new_id = ObjectId::new(new_schema, &id.name);
812                            mutations.push(Mutation::Rename(Rename {
813                                old_id: id.clone(),
814                                new_id,
815                            }));
816                            continue;
817                        }
818                        AlterTableActionFact::AddForeignKey {
819                            constraint_name,
820                            references,
821                            from_columns,
822                            to_columns,
823                            not_valid,
824                        } => {
825                            let to_table = Self::resolve_lookup_name(references, state);
826                            if !state.relation_is_present(&to_table) {
827                                return vec![Mutation::Opaque(
828                                    OpaqueMutation::UnresolvedReference {
829                                        object_kind: crate::report::violations::ObjectKind::Table,
830                                        object_name: to_table.to_string(),
831                                    },
832                                )];
833                            }
834                            AlterTableActionMutation::AddForeignKey {
835                                constraint_name: constraint_name.clone(),
836                                to_table,
837                                from_columns: from_columns.clone(),
838                                to_columns: to_columns.clone(),
839                                not_valid: *not_valid,
840                            }
841                        }
842                        AlterTableActionFact::AlterConstraint {
843                            name: c_name,
844                            deferrable,
845                        } => AlterTableActionMutation::AlterConstraint {
846                            name: c_name.clone(),
847                            deferrable: *deferrable,
848                        },
849                        AlterTableActionFact::RenameConstraint { old_name, new_name } => {
850                            AlterTableActionMutation::RenameConstraint {
851                                old_name: old_name.clone(),
852                                new_name: new_name.clone(),
853                            }
854                        }
855                        AlterTableActionFact::DropConstraint { name: c_name } => {
856                            AlterTableActionMutation::DropConstraint {
857                                name: c_name.clone(),
858                            }
859                        }
860                        AlterTableActionFact::AddCheckConstraint {
861                            constraint_name,
862                            not_valid,
863                        } => AlterTableActionMutation::AddCheckConstraint {
864                            constraint_name: constraint_name.clone(),
865                            not_valid: *not_valid,
866                        },
867                        AlterTableActionFact::AddUniqueConstraint {
868                            constraint_name,
869                            using_index,
870                        } => AlterTableActionMutation::AddUniqueConstraint {
871                            constraint_name: constraint_name.clone(),
872                            using_index: using_index
873                                .as_ref()
874                                .map(|name| Self::resolve_constraint_index_name(name, &id)),
875                        },
876                        AlterTableActionFact::AddPrimaryKeyConstraint {
877                            constraint_name,
878                            using_index,
879                        } => AlterTableActionMutation::AddPrimaryKeyConstraint {
880                            constraint_name: constraint_name.clone(),
881                            using_index: using_index
882                                .as_ref()
883                                .map(|name| Self::resolve_constraint_index_name(name, &id)),
884                        },
885                        AlterTableActionFact::AddExcludeConstraint { constraint_name } => {
886                            AlterTableActionMutation::AddExcludeConstraint {
887                                constraint_name: constraint_name.clone(),
888                            }
889                        }
890                        AlterTableActionFact::SetNotNull { column } => {
891                            AlterTableActionMutation::SetNotNull {
892                                column: column.clone(),
893                            }
894                        }
895                        AlterTableActionFact::DropNotNull { column } => {
896                            AlterTableActionMutation::DropNotNull {
897                                column: column.clone(),
898                            }
899                        }
900                        AlterTableActionFact::SetType {
901                            column,
902                            ty,
903                            has_using,
904                        } => AlterTableActionMutation::SetType {
905                            column: column.clone(),
906                            ty: ty.clone(),
907                            has_using: *has_using,
908                        },
909                        AlterTableActionFact::SetDefault { column, default } => {
910                            AlterTableActionMutation::SetDefault {
911                                column: column.clone(),
912                                default: default.clone(),
913                            }
914                        }
915                        AlterTableActionFact::ValidateConstraint { constraint_name } => {
916                            AlterTableActionMutation::ValidateConstraint {
917                                constraint_name: constraint_name.clone(),
918                            }
919                        }
920                        AlterTableActionFact::AttachPartition { child, strategy } => {
921                            let child_id = Self::resolve_lookup_name(child, state);
922
923                            AlterTableActionMutation::AttachPartition {
924                                child: child_id,
925                                strategy: strategy.clone(),
926                            }
927                        }
928                        AlterTableActionFact::DetachPartition { child } => {
929                            AlterTableActionMutation::DetachPartition {
930                                child: Self::resolve_lookup_name(child, state),
931                            }
932                        }
933                        AlterTableActionFact::SetStorage { column } => {
934                            AlterTableActionMutation::SetStorage {
935                                column: column.clone(),
936                            }
937                        }
938                        AlterTableActionFact::SetAccessMethod => {
939                            AlterTableActionMutation::SetAccessMethod
940                        }
941                        AlterTableActionFact::DisableTrigger { trigger_name } => {
942                            AlterTableActionMutation::DisableTrigger {
943                                trigger_name: trigger_name.clone(),
944                            }
945                        }
946                        AlterTableActionFact::EnableTrigger { trigger_name } => {
947                            AlterTableActionMutation::EnableTrigger {
948                                trigger_name: trigger_name.clone(),
949                            }
950                        }
951                        AlterTableActionFact::SetExpression { .. }
952                        | AlterTableActionFact::SetOptions { .. }
953                        | AlterTableActionFact::Inherit { .. }
954                        | AlterTableActionFact::NoInherit { .. }
955                        | AlterTableActionFact::ClusterOn { .. }
956                        | AlterTableActionFact::InheritTable { .. }
957                        | AlterTableActionFact::NoInheritTable { .. }
958                        | AlterTableActionFact::MergePartitions { .. }
959                        | AlterTableActionFact::SplitPartition
960                        | AlterTableActionFact::SetTablespace { .. }
961                        | AlterTableActionFact::SetLogged
962                        | AlterTableActionFact::SetUnlogged
963                        | AlterTableActionFact::ReplicaIdentity { .. }
964                        | AlterTableActionFact::ForceRls
965                        | AlterTableActionFact::EnableRls
966                        | AlterTableActionFact::DisableRls
967                        | AlterTableActionFact::EnableAlwaysTrigger { .. }
968                        | AlterTableActionFact::EnableReplicaTrigger { .. } => {
969                            AlterTableActionMutation::Opaque
970                        }
971                        AlterTableActionFact::OwnerTo { new_owner } => {
972                            AlterTableActionMutation::OwnerTo {
973                                new_owner: new_owner.clone(),
974                            }
975                        }
976                    };
977                    mutations.push(Mutation::AlterTable(AlterTable {
978                        id: id.clone(),
979                        action,
980                    }));
981                }
982            }
983            StatementFact::DropTable {
984                name,
985                if_exists,
986                cascade,
987            } => {
988                let id = Self::resolve_lookup_name(name, state);
989
990                // Still emit a DropTable mutation for rule evaluation (e.g. DriftDetectionRule)
991                // even when the table is not present locally. The state machine will handle
992                // tainting confidence in apply().
993                mutations.push(Mutation::DropTable(DropTable {
994                    id,
995                    if_exists: *if_exists,
996                    cascade: *cascade,
997                }));
998            }
999            StatementFact::DropView {
1000                name,
1001                if_exists,
1002                cascade,
1003            } => {
1004                mutations.push(Mutation::DropView(DropViewMutation {
1005                    ids: vec![Self::resolve_lookup_name(name, state)],
1006                    if_exists: *if_exists,
1007                    cascade: *cascade,
1008                }));
1009            }
1010            StatementFact::DropMaterializedView {
1011                names,
1012                if_exists,
1013                cascade,
1014            } => {
1015                let ids = names
1016                    .iter()
1017                    .map(|n| Self::resolve_lookup_name(n, state))
1018                    .collect();
1019                mutations.push(Mutation::DropMaterializedView(
1020                    DropMaterializedViewMutation {
1021                        ids,
1022                        if_exists: *if_exists,
1023                        cascade: *cascade,
1024                    },
1025                ));
1026            }
1027            StatementFact::DropIndex {
1028                names,
1029                if_exists,
1030                concurrently,
1031            } => {
1032                for name in names {
1033                    mutations.push(Mutation::DropIndex(DropIndex {
1034                        id: Self::resolve_lookup_name(name, state),
1035                        if_exists: *if_exists,
1036                        concurrently: *concurrently,
1037                    }));
1038                }
1039            }
1040            StatementFact::SetSearchPath { target, local } => {
1041                mutations.push(Mutation::SearchPath(SearchPathChange {
1042                    target: target.clone(),
1043                    local: *local,
1044                }))
1045            }
1046            StatementFact::SetTimeout {
1047                setting,
1048                value,
1049                local,
1050            } => mutations.push(Mutation::TimeoutSetting(TimeoutSettingChange {
1051                setting: *setting,
1052                value: value.clone(),
1053                local: *local,
1054            })),
1055            StatementFact::ResetSettings { target } => {
1056                mutations.push(Mutation::ResetSettings(*target))
1057            }
1058            StatementFact::BeginTransaction => mutations.push(Mutation::BeginTransaction),
1059            StatementFact::CommitTransaction => mutations.push(Mutation::CommitTransaction),
1060            StatementFact::CommitAndChain => mutations.push(Mutation::CommitAndChain),
1061            StatementFact::RollbackTransaction => mutations.push(Mutation::RollbackTransaction),
1062            StatementFact::RollbackAndChain => mutations.push(Mutation::RollbackAndChain),
1063            StatementFact::RollbackToSavepoint { name } => {
1064                mutations.push(Mutation::RollbackToSavepoint(RollbackToSavepointMutation {
1065                    name: name.clone(),
1066                }))
1067            }
1068            StatementFact::Savepoint { name } => {
1069                mutations.push(Mutation::Savepoint(SavepointMutation {
1070                    name: name.clone(),
1071                }))
1072            }
1073            StatementFact::ReleaseSavepoint { name } => {
1074                mutations.push(Mutation::ReleaseSavepoint(ReleaseSavepointMutation {
1075                    name: name.clone(),
1076                }))
1077            }
1078            StatementFact::PrepareTransaction { .. } => {
1079                mutations.push(Mutation::Opaque(OpaqueMutation::PrepareTransaction))
1080            }
1081            StatementFact::SetTransaction => {
1082                mutations.push(Mutation::Opaque(OpaqueMutation::SetTransaction))
1083            }
1084            StatementFact::SetConstraints => {
1085                mutations.push(Mutation::Opaque(OpaqueMutation::SetConstraints))
1086            }
1087            StatementFact::OpaqueBlock => mutations.push(Mutation::Opaque(OpaqueMutation::DoBlock)),
1088            StatementFact::Execute => mutations.push(Mutation::Opaque(OpaqueMutation::Execute)),
1089            StatementFact::Vacuum { relation, is_full } => {
1090                let table_id = relation
1091                    .as_ref()
1092                    .map(|r| Self::resolve_lookup_name(r, state));
1093                mutations.push(Mutation::Vacuum {
1094                    table_id,
1095                    is_full: *is_full,
1096                })
1097            }
1098            StatementFact::CreateFunction(f) => {
1099                let id = Self::resolve_function_id(&f.name, &f.params, state);
1100                mutations.push(Mutation::CreateFunction(CreateFunctionMutation {
1101                    id,
1102                    or_replace: f.or_replace,
1103                    params: f.params.clone(),
1104                    return_type: f.return_type.clone(),
1105                    options: f.options.clone(),
1106                }));
1107            }
1108            StatementFact::AlterFunction(f) => {
1109                let base_id = Self::resolve_lookup_name(&f.name, state);
1110                let sig = f.params.join(",");
1111                let id = Self::resolve_function_id_by_sig(&base_id, &sig);
1112                mutations.push(Mutation::AlterFunction(AlterFunctionMutation {
1113                    id,
1114                    action: f.action.clone(),
1115                }));
1116            }
1117            StatementFact::DropFunction(f) => {
1118                let mut signatures = Vec::new();
1119                for sig in &f.signatures {
1120                    let mut normalized_sig = sig.clone();
1121                    normalized_sig.params = normalized_sig
1122                        .params
1123                        .into_iter()
1124                        .map(|p| Self::normalize_function_arg_type(&p))
1125                        .collect();
1126                    signatures.push(normalized_sig);
1127                }
1128                mutations.push(Mutation::DropFunction(DropFunctionMutation {
1129                    signatures,
1130                    if_exists: f.if_exists,
1131                    cascade: f.cascade,
1132                }));
1133            }
1134            StatementFact::CreateProcedure(p) => {
1135                let id = Self::resolve_function_id(&p.name, &p.params, state);
1136                mutations.push(Mutation::CreateProcedure(CreateProcedureMutation {
1137                    id,
1138                    or_replace: p.or_replace,
1139                    params: p.params.clone(),
1140                    options: p.options.clone(),
1141                }));
1142            }
1143            StatementFact::AlterProcedure(p) => {
1144                let base_id = Self::resolve_lookup_name(&p.name, state);
1145                let sig = p
1146                    .params
1147                    .iter()
1148                    .map(|p| p.to_string())
1149                    .collect::<Vec<_>>()
1150                    .join(",");
1151                let id = Self::resolve_function_id_by_sig(&base_id, &sig);
1152                mutations.push(Mutation::AlterProcedure(AlterProcedureMutation {
1153                    id,
1154                    action: p.action.clone(),
1155                }));
1156            }
1157            StatementFact::DropProcedure(p) => {
1158                let signatures = p
1159                    .signatures
1160                    .iter()
1161                    .cloned()
1162                    .map(|mut signature| {
1163                        signature.params = signature
1164                            .params
1165                            .into_iter()
1166                            .map(|param| Self::normalize_function_arg_type(&param))
1167                            .collect();
1168                        signature
1169                    })
1170                    .collect();
1171                mutations.push(Mutation::DropProcedure(DropProcedureMutation {
1172                    signatures,
1173                    if_exists: p.if_exists,
1174                    cascade: p.cascade,
1175                }));
1176            }
1177            StatementFact::CreateAggregate(a) => {
1178                let id = Self::resolve_function_id(&a.name, &a.params, state);
1179                mutations.push(Mutation::CreateAggregate(CreateAggregateMutation {
1180                    id,
1181                    or_replace: a.or_replace,
1182                    params: a.params.clone(),
1183                }));
1184            }
1185            StatementFact::AlterAggregate(a) => {
1186                let base_id = Self::resolve_lookup_name(&a.name, state);
1187                let signature = a
1188                    .params
1189                    .iter()
1190                    .map(|param| Self::normalize_function_arg_type(param))
1191                    .collect::<Vec<_>>()
1192                    .join(",");
1193                let id = Self::resolve_function_id_by_sig(&base_id, &signature);
1194                mutations.push(Mutation::AlterAggregate(AlterAggregateMutation {
1195                    id,
1196                    action: a.action.clone(),
1197                }));
1198            }
1199            StatementFact::DropAggregate(a) => {
1200                let signatures = a
1201                    .signatures
1202                    .iter()
1203                    .cloned()
1204                    .map(|mut signature| {
1205                        signature.params = signature
1206                            .params
1207                            .into_iter()
1208                            .map(|param| Self::normalize_function_arg_type(&param))
1209                            .collect();
1210                        signature
1211                    })
1212                    .collect();
1213                mutations.push(Mutation::DropAggregate(DropAggregateMutation {
1214                    signatures,
1215                    if_exists: a.if_exists,
1216                    cascade: a.cascade,
1217                }));
1218            }
1219            StatementFact::CreatePublication(p) => {
1220                mutations.push(Mutation::CreatePublication(CreatePublicationMutation {
1221                    name: p.name.clone(),
1222                    scope: Self::resolve_publication_scope(&p.scope, state),
1223                    params: p.params.clone(),
1224                }));
1225            }
1226            StatementFact::AlterPublication(p) => {
1227                mutations.push(Mutation::AlterPublication(AlterPublicationMutation {
1228                    name: p.name.clone(),
1229                    action: Self::resolve_alter_publication_action(&p.action, state),
1230                }));
1231            }
1232            StatementFact::DropPublication(p) => {
1233                mutations.push(Mutation::DropPublication(DropPublicationMutation {
1234                    names: p.names.clone(),
1235                    if_exists: p.if_exists,
1236                    cascade: p.cascade,
1237                }));
1238            }
1239            StatementFact::CreateSubscription(s) => {
1240                mutations.push(Mutation::CreateSubscription(CreateSubscriptionMutation {
1241                    name: s.name.clone(),
1242                    connection: s.connection.clone(),
1243                    publications: s.publications.clone(),
1244                    params: s.params.clone(),
1245                }));
1246            }
1247            StatementFact::AlterSubscription(s) => {
1248                mutations.push(Mutation::AlterSubscription(AlterSubscriptionMutation {
1249                    name: s.name.clone(),
1250                    action: s.action.clone(),
1251                }));
1252            }
1253            StatementFact::DropSubscription(s) => {
1254                mutations.push(Mutation::DropSubscription(DropSubscriptionMutation {
1255                    name: s.name.clone(),
1256                    if_exists: s.if_exists,
1257                }));
1258            }
1259            StatementFact::CreateRole(r) => {
1260                mutations.push(Mutation::CreateRole(CreateRoleMutation {
1261                    name: r.name.clone(),
1262                    inherits: r.inherits,
1263                    can_login: r.can_login,
1264                }));
1265            }
1266            StatementFact::AlterRole(r) => {
1267                mutations.push(Mutation::AlterRole(AlterRoleMutation {
1268                    name: r.name.clone(),
1269                    inherits: r.inherits,
1270                }));
1271            }
1272            StatementFact::DropRole(r) => {
1273                mutations.push(Mutation::DropRole(DropRoleMutation {
1274                    names: r.names.clone(),
1275                    if_exists: r.if_exists,
1276                }));
1277            }
1278            StatementFact::Grant(g) => {
1279                mutations.push(Mutation::Grant(GrantMutation {
1280                    privileges: g.privileges.clone(),
1281                    target: Self::resolve_grant_target(&g.target, state),
1282                    grantees: g.grantees.clone(),
1283                    with_grant_option: g.with_grant_option,
1284                    granted_by: g.granted_by.clone(),
1285                }));
1286            }
1287            StatementFact::Revoke(r) => {
1288                mutations.push(Mutation::Revoke(RevokeMutation {
1289                    grant_option_only: r.grant_option_only,
1290                    privileges: r.privileges.clone(),
1291                    target: Self::resolve_grant_target(&r.target, state),
1292                    revokees: r.revokees.clone(),
1293                    granted_by: r.granted_by.clone(),
1294                    cascade: r.cascade,
1295                }));
1296            }
1297            StatementFact::CreateDatabase(d) => {
1298                mutations.push(Mutation::CreateDatabase(CreateDatabaseMutation {
1299                    name: d.name.clone(),
1300                    options: d.options.clone(),
1301                }));
1302            }
1303            StatementFact::AlterDatabase(d) => {
1304                let id = Self::resolve_lookup_name(&d.name, state);
1305                mutations.push(Mutation::AlterDatabase(AlterDatabaseMutation {
1306                    id,
1307                    action: d.action.clone(),
1308                }));
1309            }
1310            StatementFact::DropDatabase(d) => {
1311                let id = Self::resolve_lookup_name(&d.name, state);
1312                mutations.push(Mutation::DropDatabase(DropDatabaseMutation {
1313                    id,
1314                    if_exists: d.if_exists,
1315                }));
1316            }
1317            StatementFact::SetRole {
1318                role,
1319                local,
1320                is_session_auth,
1321            } => {
1322                mutations.push(Mutation::SwitchRole {
1323                    role: role.clone(),
1324                    local: *local,
1325                    is_session_auth: *is_session_auth,
1326                });
1327            }
1328        }
1329        mutations
1330    }
1331}