Skip to main content

safe_migrate/analysis/
resolver.rs

1// FILE: src/analysis/resolver.rs
2use crate::analysis::facts::{
3    AlterIndexActionFact, AlterTableActionFact, PersistenceFact, StatementFact, TypeCreationKind,
4};
5use crate::analysis::mutations::{
6    AlterDatabaseMutation, AlterDomainMutation, AlterFunctionMutation, AlterProcedureMutation,
7    AlterPublicationMutation, AlterRoleMutation, AlterSchemaMutation, AlterSequenceActionMutation,
8    AlterSequenceMutation, AlterSubscriptionMutation, AlterTable, AlterTableActionMutation,
9    AlterTypeActionMutation, AlterTypeMutation, ColumnMutation, CreateDatabaseMutation,
10    CreateDomainMutation, CreateFunctionMutation, CreateIndex, CreateMaterializedView,
11    CreatePolicyMutation, CreateProcedureMutation, CreatePublicationMutation, CreateRoleMutation,
12    CreateSchemaMutation, CreateSequenceMutation, CreateSubscriptionMutation, CreateTable,
13    CreateTriggerMutation, CreateTypeMutation, CreateView, DropDatabaseMutation,
14    DropDomainMutation, DropFunctionMutation, DropIndex, DropMaterializedViewMutation,
15    DropPolicyMutation, DropProcedureMutation, DropPublicationMutation, DropRoleMutation,
16    DropSchemaMutation, DropSequenceMutation, DropSubscriptionMutation, DropTable,
17    DropTriggerMutation, DropTypeMutation, DropViewMutation, FkMutation, GrantMutation, Mutation,
18    OpaqueMutation, PersistenceMutation, RefreshMaterializedViewMutation, ReleaseSavepointMutation,
19    Rename, ResolvedGrantTarget, RevokeMutation, RollbackToSavepointMutation, SavepointMutation,
20    SearchPathChange,
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            .map(|p| p.ty.clone())
128            .collect::<Vec<_>>()
129            .join(",");
130        Self::resolve_function_id_by_sig(&base_id, &sig)
131    }
132
133    fn resolve_function_id_by_sig(base_id: &ObjectId, sig: &str) -> ObjectId {
134        // Normalize types in signature to match pg_proc standard names
135        let normalized_sig = sig
136            .split(',')
137            .map(Self::normalize_function_arg_type)
138            .collect::<Vec<_>>()
139            .join(",");
140
141        let mut id = ObjectId::new(
142            base_id.schema.clone(),
143            format!("{}({})", base_id.name, normalized_sig),
144        );
145        id.inferred_schema = base_id.inferred_schema;
146        id
147    }
148
149    fn normalize_function_arg_type(raw: &str) -> String {
150        let normalized = raw.trim().to_lowercase();
151        if let Some(element_type) = normalized.strip_suffix("[]") {
152            return format!("{}[]", Self::normalize_function_arg_type(element_type));
153        }
154        match normalized.as_str() {
155            "int" | "int4" => "integer".to_string(),
156            "int8" => "bigint".to_string(),
157            "int2" => "smallint".to_string(),
158            "float8" => "double precision".to_string(),
159            "float4" => "real".to_string(),
160            "bool" => "boolean".to_string(),
161            "varchar" => "character varying".to_string(),
162            "char" => "character".to_string(),
163            "time" => "time without time zone".to_string(),
164            "timestamp" => "timestamp without time zone".to_string(),
165            "timestamptz" => "timestamp with time zone".to_string(),
166            "decimal" => "numeric".to_string(),
167            _ => normalized,
168        }
169    }
170
171    fn resolve_grant_target(
172        target: &crate::analysis::facts::GrantTarget,
173        state: &AnalysisState,
174    ) -> ResolvedGrantTarget {
175        match target {
176            crate::analysis::facts::GrantTarget::Tables(names) => ResolvedGrantTarget::Tables(
177                names
178                    .iter()
179                    .map(|n| Self::resolve_lookup_name(n, state))
180                    .collect(),
181            ),
182            crate::analysis::facts::GrantTarget::AllTablesInSchema(schemas) => {
183                ResolvedGrantTarget::AllTablesInSchema(schemas.clone())
184            }
185        }
186    }
187
188    pub fn resolve(fact: &StatementFact, state: &AnalysisState) -> Vec<Mutation> {
189        let mut mutations = Vec::new();
190        match fact {
191            StatementFact::CreateSchema {
192                name,
193                if_not_exists,
194                authorization,
195            } => {
196                mutations.push(Mutation::CreateSchema(CreateSchemaMutation {
197                    name: name.name.resolve(),
198                    if_not_exists: *if_not_exists,
199                    authorization: authorization.clone(),
200                }));
201            }
202            StatementFact::AlterSchema { name, action } => {
203                let name = name.name.resolve();
204                let action = match action {
205                    crate::analysis::facts::AlterSchemaActionFact::RenameTo { new_name } => {
206                        AlterSchemaMutation::Rename {
207                            old_name: name,
208                            new_name: new_name.resolve(),
209                        }
210                    }
211                    crate::analysis::facts::AlterSchemaActionFact::OwnerTo { new_owner } => {
212                        AlterSchemaMutation::OwnerTo {
213                            name,
214                            new_owner: new_owner.clone(),
215                        }
216                    }
217                };
218                mutations.push(Mutation::AlterSchema(action));
219            }
220            StatementFact::DropSchema {
221                names,
222                if_exists,
223                cascade,
224            } => {
225                mutations.push(Mutation::DropSchema(DropSchemaMutation {
226                    names: names.iter().map(|n| n.name.resolve()).collect(),
227                    if_exists: *if_exists,
228                    cascade: *cascade,
229                }));
230            }
231            StatementFact::CreateTable {
232                name,
233                if_not_exists,
234                as_select,
235                persistence,
236                columns,
237                foreign_keys,
238                table_constraints,
239                partition_by,
240                partition_of,
241                partition_type,
242            } => {
243                let id = Self::resolve_creation_name(name, state);
244
245                let resolved_persistence = match persistence {
246                    PersistenceFact::Permanent => PersistenceMutation::Permanent,
247                    PersistenceFact::Temporary => PersistenceMutation::Temporary,
248                    PersistenceFact::Unlogged => PersistenceMutation::Unlogged,
249                };
250
251                let col_mutations: Vec<ColumnMutation> = columns
252                    .iter()
253                    .map(|c| ColumnMutation {
254                        name: c.name.clone(),
255                        ty: c.ty.clone(),
256                        not_null: c.not_null,
257                        is_primary_key: c.is_primary_key,
258                        primary_key_constraint_name: c.primary_key_constraint_name.clone(),
259                        is_unique: c.is_unique,
260                        unique_constraint_name: c.unique_constraint_name.clone(),
261                        default: c.default.clone(),
262                        generation: c.generation,
263                    })
264                    .collect();
265
266                let mut fk_mutations = Vec::new();
267                for fk in foreign_keys {
268                    let to_table = Self::resolve_lookup_name(&fk.references, state);
269
270                    fk_mutations.push(FkMutation {
271                        constraint_name: fk.constraint_name.clone(),
272                        to_table,
273                        from_columns: fk.from_columns.clone(),
274                        to_columns: fk.to_columns.clone(),
275                    });
276                }
277
278                let partition_of_id = partition_of
279                    .as_ref()
280                    .map(|n| Self::resolve_lookup_name(n, state));
281
282                mutations.push(Mutation::CreateTable(CreateTable {
283                    id,
284                    if_not_exists: *if_not_exists,
285                    as_select: *as_select,
286                    persistence: resolved_persistence,
287                    columns: col_mutations,
288                    foreign_keys: fk_mutations,
289                    table_constraints: table_constraints.clone(),
290                    partition_by: partition_by.clone(),
291                    partition_of: partition_of_id,
292                    partition_type: partition_type.clone(),
293                }));
294            }
295            StatementFact::CreateView {
296                name,
297                or_replace,
298                depends_on,
299            } => {
300                let id = Self::resolve_creation_name(name, state);
301
302                let resolved_depends = depends_on
303                    .iter()
304                    .map(|n| Self::resolve_lookup_name(n, state))
305                    .collect();
306
307                mutations.push(Mutation::CreateView(CreateView {
308                    id,
309                    or_replace: *or_replace,
310                    depends_on: resolved_depends,
311                }));
312            }
313            StatementFact::AlterView { name, action } => {
314                match action {
315                    crate::analysis::facts::AlterViewAction::RenameTo { new_name } => {
316                        let id = Self::resolve_lookup_name(name, state);
317                        let mut new_id = ObjectId::new(id.schema.clone(), new_name.resolve());
318                        new_id.inferred_schema = id.inferred_schema;
319                        mutations.push(Mutation::Rename(Rename { old_id: id, new_id }));
320                    }
321                    crate::analysis::facts::AlterViewAction::SetSchema { new_schema } => {
322                        let id = Self::resolve_lookup_name(name, state);
323                        let new_id = ObjectId::new(new_schema, &id.name);
324                        mutations.push(Mutation::Rename(Rename { old_id: id, new_id }));
325                    }
326                    crate::analysis::facts::AlterViewAction::OwnerTo { new_owner } => {
327                        mutations.push(Mutation::ChangeRelationOwner {
328                            id: Self::resolve_lookup_name(name, state),
329                            new_owner: new_owner.clone(),
330                        });
331                    }
332                    crate::analysis::facts::AlterViewAction::SetDefault { .. }
333                    | crate::analysis::facts::AlterViewAction::DropDefault { .. }
334                    | crate::analysis::facts::AlterViewAction::RenameColumn { .. }
335                    | crate::analysis::facts::AlterViewAction::SetOptions { .. }
336                    | crate::analysis::facts::AlterViewAction::ResetOptions { .. } => {
337                        // These are opaque from the state machine's perspective —
338                        // they don't create or destroy objects, just modify metadata.
339                        // No mutation emitted; rules can still check the StatementFact.
340                    }
341                }
342            }
343            StatementFact::CreateMaterializedView { name, depends_on } => {
344                let id = Self::resolve_creation_name(name, state);
345
346                let resolved_depends = depends_on
347                    .iter()
348                    .map(|n| Self::resolve_lookup_name(n, state))
349                    .collect();
350
351                mutations.push(Mutation::CreateMaterializedView(CreateMaterializedView {
352                    id,
353                    depends_on: resolved_depends,
354                }));
355            }
356            StatementFact::AlterMaterializedView { name, new_name } => {
357                if let Some(new_name) = new_name {
358                    let id = Self::resolve_lookup_name(name, state);
359                    let mut new_id = ObjectId::new(id.schema.clone(), new_name.resolve());
360                    new_id.inferred_schema = id.inferred_schema;
361                    mutations.push(Mutation::Rename(Rename { old_id: id, new_id }));
362                }
363            }
364            StatementFact::RefreshMaterializedView { name, concurrently } => {
365                mutations.push(Mutation::RefreshMaterializedView(
366                    RefreshMaterializedViewMutation {
367                        id: Self::resolve_lookup_name(name, state),
368                        concurrently: *concurrently,
369                    },
370                ));
371            }
372            StatementFact::CreateIndex {
373                name,
374                relation,
375                if_not_exists,
376                concurrently,
377                using_method,
378                has_predicate,
379                unique,
380            } => {
381                let table = Self::resolve_lookup_name(relation, state);
382                // PostgreSQL places an unqualified index in the indexed
383                // relation's schema, not the first schema in search_path.
384                let id = if name.schema.is_some() {
385                    Self::resolve_creation_name(name, state)
386                } else {
387                    ObjectId::new(table.schema.clone(), name.name.resolve())
388                };
389
390                mutations.push(Mutation::CreateIndex(CreateIndex {
391                    id,
392                    table,
393                    if_not_exists: *if_not_exists,
394                    concurrently: *concurrently,
395                    using_method: using_method.clone(),
396                    has_predicate: *has_predicate,
397                    unique: *unique,
398                }));
399            }
400            StatementFact::CreatePolicy {
401                name,
402                table,
403                permissive,
404                command,
405            } => {
406                mutations.push(Mutation::CreatePolicy(CreatePolicyMutation {
407                    name: name.clone(),
408                    table: Self::resolve_lookup_name(table, state),
409                    permissive: *permissive,
410                    command: command.clone(),
411                }));
412            }
413            StatementFact::DropPolicy {
414                name,
415                table,
416                if_exists,
417            } => {
418                mutations.push(Mutation::DropPolicy(DropPolicyMutation {
419                    name: name.clone(),
420                    table: Self::resolve_lookup_name(table, state),
421                    if_exists: *if_exists,
422                }));
423            }
424            StatementFact::CreateTrigger {
425                name,
426                table,
427                function,
428            } => {
429                // Function references in triggers are bare names (e.g., "notify_func")
430                // but functions are stored with signature (e.g., "notify_func()").
431                // Use resolve_function_id_by_sig with empty params for consistent lookup.
432                let function_base = function
433                    .as_ref()
434                    .map(|f| Self::resolve_lookup_name(f, state))
435                    .unwrap_or_else(|| ObjectId::new("public", "unknown_function"));
436                let function_id = Self::resolve_function_id_by_sig(&function_base, "");
437                mutations.push(Mutation::CreateTrigger(CreateTriggerMutation {
438                    name: name.clone(),
439                    table: Self::resolve_lookup_name(table, state),
440                    function_id,
441                }));
442            }
443            StatementFact::DropTrigger {
444                name,
445                table,
446                if_exists,
447            } => {
448                mutations.push(Mutation::DropTrigger(DropTriggerMutation {
449                    name: name.clone(),
450                    table: Self::resolve_lookup_name(table, state),
451                    if_exists: *if_exists,
452                }));
453            }
454            StatementFact::AlterIndex { name, actions } => {
455                let id = Self::resolve_lookup_name(name, state);
456                for action in actions {
457                    match action {
458                        AlterIndexActionFact::RenameTo { new_name } => {
459                            let mut new_id = ObjectId::new(id.schema.clone(), new_name.resolve());
460                            new_id.inferred_schema = id.inferred_schema;
461                            mutations.push(Mutation::Rename(Rename {
462                                old_id: id.clone(),
463                                new_id,
464                            }));
465                        }
466                    }
467                }
468            }
469            StatementFact::CreateType(create_type) => {
470                let id = Self::resolve_creation_name(&create_type.name, state);
471
472                let mapped_kind = match &create_type.kind {
473                    TypeCreationKind::Enum { variants } => TypeKind::Enum {
474                        variants: variants.clone(),
475                    },
476                    TypeCreationKind::Range => TypeKind::Range,
477                    TypeCreationKind::Composite => TypeKind::Composite,
478                    TypeCreationKind::Base => TypeKind::Base,
479                };
480
481                mutations.push(Mutation::CreateType(CreateTypeMutation {
482                    id,
483                    kind: mapped_kind,
484                }));
485            }
486            StatementFact::AlterType(alter_type) => {
487                let id = Self::resolve_type_lookup_name(&alter_type.name, state);
488                for action_fact in &alter_type.actions {
489                    match action_fact {
490                        crate::analysis::facts::AlterTypeActionFact::AddValue {
491                            new_value,
492                            neighbor,
493                            before,
494                        } => {
495                            mutations.push(Mutation::AlterType(AlterTypeMutation {
496                                id: id.clone(),
497                                action: AlterTypeActionMutation::AddValue {
498                                    new_value: new_value.clone(),
499                                    neighbor: neighbor.clone(),
500                                    before: *before,
501                                },
502                            }));
503                        }
504                        crate::analysis::facts::AlterTypeActionFact::RenameValue {
505                            old_value,
506                            new_value,
507                        } => {
508                            mutations.push(Mutation::AlterType(AlterTypeMutation {
509                                id: id.clone(),
510                                action: AlterTypeActionMutation::RenameValue {
511                                    old_value: old_value.clone(),
512                                    new_value: new_value.clone(),
513                                },
514                            }));
515                        }
516                    }
517                }
518            }
519            StatementFact::CreateDomain { name, base_type } => {
520                let id = Self::resolve_creation_name(name, state);
521
522                mutations.push(Mutation::CreateDomain(CreateDomainMutation {
523                    id,
524                    base_type: base_type.clone(),
525                }));
526            }
527            StatementFact::AlterDomain { name, action } => {
528                mutations.push(Mutation::AlterDomain(AlterDomainMutation {
529                    id: Self::resolve_lookup_name(name, state),
530                    action: action.clone(),
531                }));
532            }
533            StatementFact::DropDomain {
534                names,
535                if_exists,
536                cascade,
537            } => {
538                let ids = names
539                    .iter()
540                    .map(|n| Self::resolve_lookup_name(n, state))
541                    .collect();
542                mutations.push(Mutation::DropDomain(DropDomainMutation {
543                    ids,
544                    if_exists: *if_exists,
545                    cascade: *cascade,
546                }));
547            }
548            StatementFact::DropType {
549                names,
550                if_exists,
551                cascade,
552            } => {
553                let ids = names
554                    .iter()
555                    .map(|n| Self::resolve_lookup_name(n, state))
556                    .collect();
557                mutations.push(Mutation::DropType(DropTypeMutation {
558                    ids,
559                    if_exists: *if_exists,
560                    cascade: *cascade,
561                }));
562            }
563            StatementFact::CreateSequence {
564                name,
565                if_not_exists,
566                owned_by,
567            } => {
568                let id = Self::resolve_creation_name(name, state);
569
570                let resolved_owned_by = owned_by.as_ref().map(|(table_name, col)| {
571                    (Self::resolve_lookup_name(table_name, state), col.clone())
572                });
573                mutations.push(Mutation::CreateSequence(CreateSequenceMutation {
574                    id,
575                    if_not_exists: *if_not_exists,
576                    owned_by: resolved_owned_by,
577                }));
578            }
579            StatementFact::AlterSequence {
580                name,
581                if_exists,
582                action,
583            } => {
584                let id = Self::resolve_lookup_name(name, state);
585                let action = match action {
586                    crate::analysis::facts::AlterSequenceActionFact::OwnedBy(owned_by) => {
587                        AlterSequenceActionMutation::OwnedBy(owned_by.as_ref().map(
588                            |(table_name, col)| {
589                                (Self::resolve_lookup_name(table_name, state), col.clone())
590                            },
591                        ))
592                    }
593                    crate::analysis::facts::AlterSequenceActionFact::OwnerTo(owner) => {
594                        AlterSequenceActionMutation::OwnerTo(owner.clone())
595                    }
596                    crate::analysis::facts::AlterSequenceActionFact::RenameTo(new_name) => {
597                        AlterSequenceActionMutation::RenameTo(ObjectId::new(
598                            &id.schema,
599                            new_name.resolve(),
600                        ))
601                    }
602                    crate::analysis::facts::AlterSequenceActionFact::SetSchema(schema) => {
603                        AlterSequenceActionMutation::SetSchema(ObjectId::new(schema, &id.name))
604                    }
605                    crate::analysis::facts::AlterSequenceActionFact::Other => {
606                        AlterSequenceActionMutation::Other
607                    }
608                };
609                mutations.push(Mutation::AlterSequence(AlterSequenceMutation {
610                    id,
611                    if_exists: *if_exists,
612                    action,
613                }));
614            }
615            StatementFact::DropSequence {
616                names,
617                if_exists,
618                cascade,
619            } => {
620                let ids = names
621                    .iter()
622                    .map(|n| Self::resolve_lookup_name(n, state))
623                    .collect();
624                mutations.push(Mutation::DropSequence(DropSequenceMutation {
625                    ids,
626                    if_exists: *if_exists,
627                    cascade: *cascade,
628                }));
629            }
630            StatementFact::AlterTable { name, actions } => {
631                let id = Self::resolve_lookup_name(name, state);
632                for action_fact in actions {
633                    let action = match action_fact {
634                        AlterTableActionFact::AddColumn {
635                            name: col_name,
636                            ty,
637                            if_not_exists,
638                            not_null,
639                            default,
640                            generation,
641                        } => AlterTableActionMutation::AddColumn {
642                            name: col_name.clone(),
643                            ty: ty.clone(),
644                            if_not_exists: *if_not_exists,
645                            not_null: *not_null,
646                            default: default.clone(),
647                            depends_on: None, // Logic for extraction can be added later if needed
648                            generation: *generation,
649                        },
650                        AlterTableActionFact::DropColumn {
651                            name: col_name,
652                            if_exists,
653                        } => AlterTableActionMutation::DropColumn {
654                            name: col_name.clone(),
655                            if_exists: *if_exists,
656                        },
657                        AlterTableActionFact::RenameColumn { from, to } => {
658                            AlterTableActionMutation::RenameColumn {
659                                from: from.resolve(),
660                                to: to.resolve(),
661                            }
662                        }
663                        AlterTableActionFact::RenameTo { new_name } => {
664                            let mut new_id = ObjectId::new(id.schema.clone(), new_name.resolve());
665                            new_id.inferred_schema = id.inferred_schema;
666                            mutations.push(Mutation::Rename(Rename {
667                                old_id: id.clone(),
668                                new_id,
669                            }));
670                            continue;
671                        }
672                        AlterTableActionFact::SetSchema { new_schema } => {
673                            let new_id = ObjectId::new(new_schema, &id.name);
674                            mutations.push(Mutation::Rename(Rename {
675                                old_id: id.clone(),
676                                new_id,
677                            }));
678                            continue;
679                        }
680                        AlterTableActionFact::AddForeignKey {
681                            constraint_name,
682                            references,
683                            from_columns,
684                            to_columns,
685                            not_valid,
686                        } => {
687                            let to_table = Self::resolve_lookup_name(references, state);
688                            if !state.relation_is_present(&to_table) {
689                                return vec![Mutation::Opaque(
690                                    OpaqueMutation::UnresolvedReference {
691                                        object_kind: crate::report::violations::ObjectKind::Table,
692                                        object_name: to_table.to_string(),
693                                    },
694                                )];
695                            }
696                            AlterTableActionMutation::AddForeignKey {
697                                constraint_name: constraint_name.clone(),
698                                to_table,
699                                from_columns: from_columns.clone(),
700                                to_columns: to_columns.clone(),
701                                not_valid: *not_valid,
702                            }
703                        }
704                        AlterTableActionFact::AlterConstraint {
705                            name: c_name,
706                            deferrable,
707                        } => AlterTableActionMutation::AlterConstraint {
708                            name: c_name.clone(),
709                            deferrable: *deferrable,
710                        },
711                        AlterTableActionFact::RenameConstraint { old_name, new_name } => {
712                            AlterTableActionMutation::RenameConstraint {
713                                old_name: old_name.clone(),
714                                new_name: new_name.clone(),
715                            }
716                        }
717                        AlterTableActionFact::DropConstraint { name: c_name } => {
718                            AlterTableActionMutation::DropConstraint {
719                                name: c_name.clone(),
720                            }
721                        }
722                        AlterTableActionFact::AddCheckConstraint {
723                            constraint_name,
724                            not_valid,
725                        } => AlterTableActionMutation::AddCheckConstraint {
726                            constraint_name: constraint_name.clone(),
727                            not_valid: *not_valid,
728                        },
729                        AlterTableActionFact::AddUniqueConstraint {
730                            constraint_name,
731                            using_index,
732                        } => AlterTableActionMutation::AddUniqueConstraint {
733                            constraint_name: constraint_name.clone(),
734                            using_index: using_index
735                                .as_ref()
736                                .map(|name| Self::resolve_constraint_index_name(name, &id)),
737                        },
738                        AlterTableActionFact::AddPrimaryKeyConstraint {
739                            constraint_name,
740                            using_index,
741                        } => AlterTableActionMutation::AddPrimaryKeyConstraint {
742                            constraint_name: constraint_name.clone(),
743                            using_index: using_index
744                                .as_ref()
745                                .map(|name| Self::resolve_constraint_index_name(name, &id)),
746                        },
747                        AlterTableActionFact::AddExcludeConstraint { constraint_name } => {
748                            AlterTableActionMutation::AddExcludeConstraint {
749                                constraint_name: constraint_name.clone(),
750                            }
751                        }
752                        AlterTableActionFact::SetNotNull { column } => {
753                            AlterTableActionMutation::SetNotNull {
754                                column: column.clone(),
755                            }
756                        }
757                        AlterTableActionFact::DropNotNull { column } => {
758                            AlterTableActionMutation::DropNotNull {
759                                column: column.clone(),
760                            }
761                        }
762                        AlterTableActionFact::SetType {
763                            column,
764                            ty,
765                            has_using,
766                        } => AlterTableActionMutation::SetType {
767                            column: column.clone(),
768                            ty: ty.clone(),
769                            has_using: *has_using,
770                        },
771                        AlterTableActionFact::SetDefault { column, default } => {
772                            AlterTableActionMutation::SetDefault {
773                                column: column.clone(),
774                                default: default.clone(),
775                            }
776                        }
777                        AlterTableActionFact::ValidateConstraint { constraint_name } => {
778                            AlterTableActionMutation::ValidateConstraint {
779                                constraint_name: constraint_name.clone(),
780                            }
781                        }
782                        AlterTableActionFact::AttachPartition { child, strategy } => {
783                            let child_id = Self::resolve_lookup_name(child, state);
784
785                            AlterTableActionMutation::AttachPartition {
786                                child: child_id,
787                                strategy: strategy.clone(),
788                            }
789                        }
790                        AlterTableActionFact::DetachPartition { child } => {
791                            AlterTableActionMutation::DetachPartition {
792                                child: Self::resolve_lookup_name(child, state),
793                            }
794                        }
795                        AlterTableActionFact::SetStorage { column } => {
796                            AlterTableActionMutation::SetStorage {
797                                column: column.clone(),
798                            }
799                        }
800                        AlterTableActionFact::SetAccessMethod => {
801                            AlterTableActionMutation::SetAccessMethod
802                        }
803                        AlterTableActionFact::DisableTrigger { trigger_name } => {
804                            AlterTableActionMutation::DisableTrigger {
805                                trigger_name: trigger_name.clone(),
806                            }
807                        }
808                        AlterTableActionFact::EnableTrigger { trigger_name } => {
809                            AlterTableActionMutation::EnableTrigger {
810                                trigger_name: trigger_name.clone(),
811                            }
812                        }
813                        AlterTableActionFact::SetExpression { .. }
814                        | AlterTableActionFact::SetOptions { .. }
815                        | AlterTableActionFact::Inherit { .. }
816                        | AlterTableActionFact::NoInherit { .. }
817                        | AlterTableActionFact::ClusterOn { .. }
818                        | AlterTableActionFact::InheritTable { .. }
819                        | AlterTableActionFact::NoInheritTable { .. }
820                        | AlterTableActionFact::MergePartitions { .. }
821                        | AlterTableActionFact::SplitPartition
822                        | AlterTableActionFact::SetTablespace { .. }
823                        | AlterTableActionFact::SetLogged
824                        | AlterTableActionFact::SetUnlogged
825                        | AlterTableActionFact::ReplicaIdentity { .. }
826                        | AlterTableActionFact::ForceRls
827                        | AlterTableActionFact::EnableRls
828                        | AlterTableActionFact::DisableRls
829                        | AlterTableActionFact::EnableAlwaysTrigger { .. }
830                        | AlterTableActionFact::EnableReplicaTrigger { .. } => {
831                            AlterTableActionMutation::Opaque
832                        }
833                        AlterTableActionFact::OwnerTo { new_owner } => {
834                            AlterTableActionMutation::OwnerTo {
835                                new_owner: new_owner.clone(),
836                            }
837                        }
838                    };
839                    mutations.push(Mutation::AlterTable(AlterTable {
840                        id: id.clone(),
841                        action,
842                    }));
843                }
844            }
845            StatementFact::DropTable {
846                name,
847                if_exists,
848                cascade,
849            } => {
850                let id = Self::resolve_lookup_name(name, state);
851
852                // Still emit a DropTable mutation for rule evaluation (e.g. DriftDetectionRule)
853                // even when the table is not present locally. The state machine will handle
854                // tainting confidence in apply().
855                mutations.push(Mutation::DropTable(DropTable {
856                    id,
857                    if_exists: *if_exists,
858                    cascade: *cascade,
859                }));
860            }
861            StatementFact::DropView {
862                name,
863                if_exists,
864                cascade,
865            } => {
866                mutations.push(Mutation::DropView(DropViewMutation {
867                    ids: vec![Self::resolve_lookup_name(name, state)],
868                    if_exists: *if_exists,
869                    cascade: *cascade,
870                }));
871            }
872            StatementFact::DropMaterializedView {
873                names,
874                if_exists,
875                cascade,
876            } => {
877                let ids = names
878                    .iter()
879                    .map(|n| Self::resolve_lookup_name(n, state))
880                    .collect();
881                mutations.push(Mutation::DropMaterializedView(
882                    DropMaterializedViewMutation {
883                        ids,
884                        if_exists: *if_exists,
885                        cascade: *cascade,
886                    },
887                ));
888            }
889            StatementFact::DropIndex {
890                names,
891                if_exists,
892                concurrently,
893            } => {
894                for name in names {
895                    mutations.push(Mutation::DropIndex(DropIndex {
896                        id: Self::resolve_lookup_name(name, state),
897                        if_exists: *if_exists,
898                        concurrently: *concurrently,
899                    }));
900                }
901            }
902            StatementFact::SetSearchPath { target } => {
903                mutations.push(Mutation::SearchPath(SearchPathChange {
904                    target: target.clone(),
905                }))
906            }
907            StatementFact::BeginTransaction => mutations.push(Mutation::BeginTransaction),
908            StatementFact::CommitTransaction => mutations.push(Mutation::CommitTransaction),
909            StatementFact::CommitAndChain => mutations.push(Mutation::CommitAndChain),
910            StatementFact::RollbackTransaction => mutations.push(Mutation::RollbackTransaction),
911            StatementFact::RollbackAndChain => mutations.push(Mutation::RollbackAndChain),
912            StatementFact::RollbackToSavepoint { name } => {
913                mutations.push(Mutation::RollbackToSavepoint(RollbackToSavepointMutation {
914                    name: name.clone(),
915                }))
916            }
917            StatementFact::Savepoint { name } => {
918                mutations.push(Mutation::Savepoint(SavepointMutation {
919                    name: name.clone(),
920                }))
921            }
922            StatementFact::ReleaseSavepoint { name } => {
923                mutations.push(Mutation::ReleaseSavepoint(ReleaseSavepointMutation {
924                    name: name.clone(),
925                }))
926            }
927            StatementFact::PrepareTransaction { .. } => {
928                mutations.push(Mutation::Opaque(OpaqueMutation::PrepareTransaction))
929            }
930            StatementFact::SetTransaction => {
931                mutations.push(Mutation::Opaque(OpaqueMutation::SetTransaction))
932            }
933            StatementFact::SetConstraints => {
934                mutations.push(Mutation::Opaque(OpaqueMutation::SetConstraints))
935            }
936            StatementFact::OpaqueBlock => mutations.push(Mutation::Opaque(OpaqueMutation::DoBlock)),
937            StatementFact::Execute => mutations.push(Mutation::Opaque(OpaqueMutation::Execute)),
938            StatementFact::Vacuum { relation, is_full } => {
939                let table_id = relation
940                    .as_ref()
941                    .map(|r| Self::resolve_lookup_name(r, state));
942                mutations.push(Mutation::Vacuum {
943                    table_id,
944                    is_full: *is_full,
945                })
946            }
947            StatementFact::CreateFunction(f) => {
948                let id = Self::resolve_function_id(&f.name, &f.params, state);
949                mutations.push(Mutation::CreateFunction(CreateFunctionMutation {
950                    id,
951                    or_replace: f.or_replace,
952                    params: f.params.clone(),
953                    return_type: f.return_type.clone(),
954                    options: f.options.clone(),
955                }));
956            }
957            StatementFact::AlterFunction(f) => {
958                let base_id = Self::resolve_lookup_name(&f.name, state);
959                let sig = f.params.join(",");
960                let id = Self::resolve_function_id_by_sig(&base_id, &sig);
961                mutations.push(Mutation::AlterFunction(AlterFunctionMutation {
962                    id,
963                    action: f.action.clone(),
964                }));
965            }
966            StatementFact::DropFunction(f) => {
967                let mut signatures = Vec::new();
968                for sig in &f.signatures {
969                    let mut normalized_sig = sig.clone();
970                    normalized_sig.params = normalized_sig
971                        .params
972                        .into_iter()
973                        .map(|p| Self::normalize_function_arg_type(&p))
974                        .collect();
975                    signatures.push(normalized_sig);
976                }
977                mutations.push(Mutation::DropFunction(DropFunctionMutation {
978                    signatures,
979                    if_exists: f.if_exists,
980                    cascade: f.cascade,
981                }));
982            }
983            StatementFact::CreateProcedure(p) => {
984                let id = Self::resolve_function_id(&p.name, &p.params, state);
985                mutations.push(Mutation::CreateProcedure(CreateProcedureMutation {
986                    id,
987                    or_replace: p.or_replace,
988                    params: p.params.clone(),
989                    options: p.options.clone(),
990                }));
991            }
992            StatementFact::AlterProcedure(p) => {
993                let base_id = Self::resolve_lookup_name(&p.name, state);
994                let sig = p
995                    .params
996                    .iter()
997                    .map(|p| p.to_string())
998                    .collect::<Vec<_>>()
999                    .join(",");
1000                let id = Self::resolve_function_id_by_sig(&base_id, &sig);
1001                mutations.push(Mutation::AlterProcedure(AlterProcedureMutation {
1002                    id,
1003                    action: p.action.clone(),
1004                }));
1005            }
1006            StatementFact::DropProcedure(p) => {
1007                mutations.push(Mutation::DropProcedure(DropProcedureMutation {
1008                    signatures: p.signatures.clone(),
1009                    if_exists: p.if_exists,
1010                    cascade: p.cascade,
1011                }));
1012            }
1013            StatementFact::CreatePublication(p) => {
1014                mutations.push(Mutation::CreatePublication(CreatePublicationMutation {
1015                    name: p.name.clone(),
1016                    scope: p.scope.clone(),
1017                    params: p.params.clone(),
1018                }));
1019            }
1020            StatementFact::AlterPublication(p) => {
1021                mutations.push(Mutation::AlterPublication(AlterPublicationMutation {
1022                    name: p.name.clone(),
1023                }));
1024            }
1025            StatementFact::DropPublication(p) => {
1026                mutations.push(Mutation::DropPublication(DropPublicationMutation {
1027                    names: p.names.clone(),
1028                    if_exists: p.if_exists,
1029                    cascade: p.cascade,
1030                }));
1031            }
1032            StatementFact::CreateSubscription(s) => {
1033                mutations.push(Mutation::CreateSubscription(CreateSubscriptionMutation {
1034                    name: s.name.clone(),
1035                    connection: s.connection.clone(),
1036                    publications: s.publications.clone(),
1037                    params: s.params.clone(),
1038                }));
1039            }
1040            StatementFact::AlterSubscription(s) => {
1041                mutations.push(Mutation::AlterSubscription(AlterSubscriptionMutation {
1042                    name: s.name.clone(),
1043                }));
1044            }
1045            StatementFact::DropSubscription(s) => {
1046                mutations.push(Mutation::DropSubscription(DropSubscriptionMutation {
1047                    name: s.name.clone(),
1048                    if_exists: s.if_exists,
1049                }));
1050            }
1051            StatementFact::CreateRole(r) => {
1052                mutations.push(Mutation::CreateRole(CreateRoleMutation {
1053                    name: r.name.clone(),
1054                    inherits: r.inherits,
1055                }));
1056            }
1057            StatementFact::AlterRole(r) => {
1058                mutations.push(Mutation::AlterRole(AlterRoleMutation {
1059                    name: r.name.clone(),
1060                    inherits: r.inherits,
1061                }));
1062            }
1063            StatementFact::DropRole(r) => {
1064                mutations.push(Mutation::DropRole(DropRoleMutation {
1065                    names: r.names.clone(),
1066                    if_exists: r.if_exists,
1067                }));
1068            }
1069            StatementFact::Grant(g) => {
1070                mutations.push(Mutation::Grant(GrantMutation {
1071                    privileges: g.privileges.clone(),
1072                    target: Self::resolve_grant_target(&g.target, state),
1073                    grantees: g.grantees.clone(),
1074                    with_grant_option: g.with_grant_option,
1075                    granted_by: g.granted_by.clone(),
1076                }));
1077            }
1078            StatementFact::Revoke(r) => {
1079                mutations.push(Mutation::Revoke(RevokeMutation {
1080                    grant_option_only: r.grant_option_only,
1081                    privileges: r.privileges.clone(),
1082                    target: Self::resolve_grant_target(&r.target, state),
1083                    revokees: r.revokees.clone(),
1084                    granted_by: r.granted_by.clone(),
1085                    cascade: r.cascade,
1086                }));
1087            }
1088            StatementFact::CreateDatabase(d) => {
1089                mutations.push(Mutation::CreateDatabase(CreateDatabaseMutation {
1090                    name: d.name.clone(),
1091                    options: d.options.clone(),
1092                }));
1093            }
1094            StatementFact::AlterDatabase(d) => {
1095                let id = Self::resolve_lookup_name(&d.name, state);
1096                mutations.push(Mutation::AlterDatabase(AlterDatabaseMutation {
1097                    id,
1098                    action: d.action.clone(),
1099                }));
1100            }
1101            StatementFact::DropDatabase(d) => {
1102                let id = Self::resolve_lookup_name(&d.name, state);
1103                mutations.push(Mutation::DropDatabase(DropDatabaseMutation {
1104                    id,
1105                    if_exists: d.if_exists,
1106                }));
1107            }
1108            StatementFact::SetRole {
1109                role,
1110                local,
1111                is_session_auth,
1112            } => {
1113                mutations.push(Mutation::SwitchRole {
1114                    role: role.clone(),
1115                    local: *local,
1116                    is_session_auth: *is_session_auth,
1117                });
1118            }
1119        }
1120        mutations
1121    }
1122}