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, AlterSequenceMutation, AlterSubscriptionMutation,
8    AlterTable, AlterTableActionMutation, AlterTypeActionMutation, AlterTypeMutation,
9    ColumnMutation, CreateDatabaseMutation, CreateDomainMutation, CreateFunctionMutation,
10    CreateIndex, CreateMaterializedView, CreatePolicyMutation, CreateProcedureMutation,
11    CreatePublicationMutation, CreateRoleMutation, CreateSchemaMutation, CreateSequenceMutation,
12    CreateSubscriptionMutation, CreateTable, CreateTriggerMutation, CreateTypeMutation, CreateView,
13    DropDatabaseMutation, DropDomainMutation, DropFunctionMutation, DropIndex,
14    DropMaterializedViewMutation, DropPolicyMutation, DropProcedureMutation,
15    DropPublicationMutation, DropRoleMutation, DropSchemaMutation, DropSequenceMutation,
16    DropSubscriptionMutation, DropTable, DropTriggerMutation, DropTypeMutation, DropViewMutation,
17    FkMutation, GrantMutation, Mutation, OpaqueMutation, PersistenceMutation,
18    RefreshMaterializedViewMutation, ReleaseSavepointMutation, Rename, ResolvedGrantTarget,
19    RevokeMutation, RollbackToSavepointMutation, SavepointMutation, SearchPathChange,
20};
21use crate::analysis::state::AnalysisState;
22use crate::ast::identifiers::{ObjectId, QualifiedName};
23use crate::model::types::TypeKind;
24
25pub struct Resolver;
26
27impl Resolver {
28    fn resolve_creation_name(name: &QualifiedName, state: &AnalysisState) -> ObjectId {
29        let schema = name
30            .schema
31            .as_ref()
32            .map(|i| i.resolve())
33            .unwrap_or_else(|| {
34                state
35                    .local
36                    .search_path
37                    .first()
38                    .map(|s| s.as_str())
39                    .unwrap_or("public")
40                    .to_string()
41            });
42
43        ObjectId::new(schema, name.name.resolve())
44    }
45
46    fn resolve_lookup_name(name: &QualifiedName, state: &AnalysisState) -> ObjectId {
47        if let Some(schema_ident) = &name.schema {
48            return ObjectId::new(schema_ident.resolve(), name.name.resolve());
49        }
50
51        let resolved_name = name.name.resolve();
52
53        for schema in &state.local.search_path {
54            let mut candidate = ObjectId::new(schema.clone(), resolved_name.clone());
55            if state.local.relations.contains_key(&candidate)
56                || state.local.types.contains_key(&candidate)
57                || state.local.sequences.contains_key(&candidate)
58                || state.local.functions.keys().any(|k| {
59                    k.schema == candidate.schema
60                        && (k.name == candidate.name
61                            || k.name.starts_with(&format!("{}(", candidate.name)))
62                })
63            {
64                candidate.inferred_schema = true;
65                return candidate;
66            }
67        }
68
69        let schema = state
70            .local
71            .search_path
72            .first()
73            .map(|s| s.as_str())
74            .unwrap_or("public")
75            .to_string();
76        let mut id = ObjectId::new(schema, resolved_name);
77        id.inferred_schema = true;
78        id
79    }
80
81    fn resolve_function_id(
82        name: &QualifiedName,
83        params: &[crate::analysis::facts::ParamFact],
84        state: &AnalysisState,
85    ) -> ObjectId {
86        let base_id = Self::resolve_creation_name(name, state);
87        let sig = params
88            .iter()
89            .map(|p| p.ty.clone())
90            .collect::<Vec<_>>()
91            .join(",");
92        Self::resolve_function_id_by_sig(&base_id, &sig)
93    }
94
95    fn resolve_function_id_by_sig(base_id: &ObjectId, sig: &str) -> ObjectId {
96        // Normalize types in signature to match pg_proc standard names
97        let normalized_sig = sig
98            .split(',')
99            .map(|s| {
100                let s = s.trim().to_lowercase();
101                match s.as_str() {
102                    "int" | "int4" => "integer".to_string(),
103                    "int8" => "bigint".to_string(),
104                    "int2" => "smallint".to_string(),
105                    "float8" => "double precision".to_string(),
106                    "float4" => "real".to_string(),
107                    "bool" => "boolean".to_string(),
108                    "varchar" => "character varying".to_string(),
109                    "char" => "character".to_string(),
110                    "time" => "time without time zone".to_string(),
111                    "timestamp" => "timestamp without time zone".to_string(),
112                    "timestamptz" => "timestamp with time zone".to_string(),
113                    "decimal" => "numeric".to_string(),
114                    _ => s,
115                }
116            })
117            .collect::<Vec<_>>()
118            .join(",");
119
120        let mut id = ObjectId::new(
121            base_id.schema.clone(),
122            format!("{}({})", base_id.name, normalized_sig),
123        );
124        id.inferred_schema = base_id.inferred_schema;
125        id
126    }
127
128    fn resolve_grant_target(
129        target: &crate::analysis::facts::GrantTarget,
130        state: &AnalysisState,
131    ) -> ResolvedGrantTarget {
132        match target {
133            crate::analysis::facts::GrantTarget::Tables(names) => ResolvedGrantTarget::Tables(
134                names
135                    .iter()
136                    .map(|n| Self::resolve_lookup_name(n, state))
137                    .collect(),
138            ),
139            crate::analysis::facts::GrantTarget::AllTablesInSchema(schemas) => {
140                ResolvedGrantTarget::AllTablesInSchema(schemas.clone())
141            }
142        }
143    }
144
145    pub fn resolve(fact: &StatementFact, state: &AnalysisState) -> Vec<Mutation> {
146        let mut mutations = Vec::new();
147        match fact {
148            StatementFact::CreateSchema {
149                name,
150                if_not_exists,
151            } => {
152                mutations.push(Mutation::CreateSchema(CreateSchemaMutation {
153                    name: name.name.resolve(),
154                    if_not_exists: *if_not_exists,
155                }));
156            }
157            StatementFact::AlterSchema { name, new_name } => {
158                if let Some(nn) = new_name {
159                    let id = Self::resolve_lookup_name(name, state);
160                    let mut new_id = ObjectId::new(id.schema.clone(), nn.resolve());
161                    new_id.inferred_schema = id.inferred_schema;
162                    mutations.push(Mutation::Rename(Rename { old_id: id, new_id }));
163                }
164            }
165            StatementFact::DropSchema {
166                names,
167                if_exists,
168                cascade,
169            } => {
170                mutations.push(Mutation::DropSchema(DropSchemaMutation {
171                    names: names.iter().map(|n| n.name.resolve()).collect(),
172                    if_exists: *if_exists,
173                    cascade: *cascade,
174                }));
175            }
176            StatementFact::CreateTable {
177                name,
178                if_not_exists,
179                as_select,
180                persistence,
181                columns,
182                foreign_keys,
183                table_constraints,
184                partition_by,
185                partition_of,
186                partition_type,
187            } => {
188                let id = Self::resolve_creation_name(name, state);
189
190                let resolved_persistence = match persistence {
191                    PersistenceFact::Permanent => PersistenceMutation::Permanent,
192                    PersistenceFact::Temporary => PersistenceMutation::Temporary,
193                    PersistenceFact::Unlogged => PersistenceMutation::Unlogged,
194                };
195
196                let col_mutations: Vec<ColumnMutation> = columns
197                    .iter()
198                    .map(|c| ColumnMutation {
199                        name: c.name.clone(),
200                        ty: c.ty.clone(),
201                        not_null: c.not_null,
202                        is_primary_key: c.is_primary_key,
203                        default: c.default.clone(),
204                    })
205                    .collect();
206
207                let mut fk_mutations = Vec::new();
208                for fk in foreign_keys {
209                    let to_table = Self::resolve_lookup_name(&fk.references, state);
210
211                    fk_mutations.push(FkMutation {
212                        constraint_name: fk.constraint_name.clone(),
213                        to_table,
214                        from_columns: fk.from_columns.clone(),
215                        to_columns: fk.to_columns.clone(),
216                    });
217                }
218
219                let partition_of_id = partition_of
220                    .as_ref()
221                    .map(|n| Self::resolve_lookup_name(n, state));
222
223                mutations.push(Mutation::CreateTable(CreateTable {
224                    id,
225                    if_not_exists: *if_not_exists,
226                    as_select: *as_select,
227                    persistence: resolved_persistence,
228                    columns: col_mutations,
229                    foreign_keys: fk_mutations,
230                    table_constraints: table_constraints.clone(),
231                    partition_by: partition_by.clone(),
232                    partition_of: partition_of_id,
233                    partition_type: partition_type.clone(),
234                }));
235            }
236            StatementFact::CreateView {
237                name,
238                or_replace,
239                depends_on,
240            } => {
241                let id = Self::resolve_creation_name(name, state);
242
243                let resolved_depends = depends_on
244                    .iter()
245                    .map(|n| Self::resolve_lookup_name(n, state))
246                    .collect();
247
248                mutations.push(Mutation::CreateView(CreateView {
249                    id,
250                    or_replace: *or_replace,
251                    depends_on: resolved_depends,
252                }));
253            }
254            StatementFact::AlterView { name, action } => {
255                match action {
256                    crate::analysis::facts::AlterViewAction::RenameTo { new_name } => {
257                        let id = Self::resolve_lookup_name(name, state);
258                        let mut new_id = ObjectId::new(id.schema.clone(), new_name.resolve());
259                        new_id.inferred_schema = id.inferred_schema;
260                        mutations.push(Mutation::Rename(Rename { old_id: id, new_id }));
261                    }
262                    crate::analysis::facts::AlterViewAction::SetSchema { .. } => {
263                        // SET SCHEMA — rename tracked at object-id level is handled by state machine
264                        // No mutation needed since schema changes don't change ObjectId
265                    }
266                    crate::analysis::facts::AlterViewAction::OwnerTo { .. }
267                    | crate::analysis::facts::AlterViewAction::SetDefault { .. }
268                    | crate::analysis::facts::AlterViewAction::DropDefault { .. }
269                    | crate::analysis::facts::AlterViewAction::RenameColumn { .. }
270                    | crate::analysis::facts::AlterViewAction::SetOptions { .. }
271                    | crate::analysis::facts::AlterViewAction::ResetOptions { .. } => {
272                        // These are opaque from the state machine's perspective —
273                        // they don't create or destroy objects, just modify metadata.
274                        // No mutation emitted; rules can still check the StatementFact.
275                    }
276                }
277            }
278            StatementFact::CreateMaterializedView { name, depends_on } => {
279                let id = Self::resolve_creation_name(name, state);
280
281                let resolved_depends = depends_on
282                    .iter()
283                    .map(|n| Self::resolve_lookup_name(n, state))
284                    .collect();
285
286                mutations.push(Mutation::CreateMaterializedView(CreateMaterializedView {
287                    id,
288                    depends_on: resolved_depends,
289                }));
290            }
291            StatementFact::AlterMaterializedView { name, new_name } => {
292                if let Some(new_name) = new_name {
293                    let id = Self::resolve_lookup_name(name, state);
294                    let mut new_id = ObjectId::new(id.schema.clone(), new_name.resolve());
295                    new_id.inferred_schema = id.inferred_schema;
296                    mutations.push(Mutation::Rename(Rename { old_id: id, new_id }));
297                }
298            }
299            StatementFact::RefreshMaterializedView { name, concurrently } => {
300                mutations.push(Mutation::RefreshMaterializedView(
301                    RefreshMaterializedViewMutation {
302                        id: Self::resolve_lookup_name(name, state),
303                        concurrently: *concurrently,
304                    },
305                ));
306            }
307            StatementFact::CreateIndex {
308                name,
309                relation,
310                if_not_exists,
311                concurrently,
312                using_method,
313                has_predicate,
314                unique,
315            } => {
316                let id = Self::resolve_creation_name(name, state);
317
318                mutations.push(Mutation::CreateIndex(CreateIndex {
319                    id,
320                    table: Self::resolve_lookup_name(relation, state),
321                    if_not_exists: *if_not_exists,
322                    concurrently: *concurrently,
323                    using_method: using_method.clone(),
324                    has_predicate: *has_predicate,
325                    unique: *unique,
326                }));
327            }
328            StatementFact::CreatePolicy {
329                name,
330                table,
331                permissive,
332                command,
333            } => {
334                mutations.push(Mutation::CreatePolicy(CreatePolicyMutation {
335                    name: name.clone(),
336                    table: Self::resolve_lookup_name(table, state),
337                    permissive: *permissive,
338                    command: command.clone(),
339                }));
340            }
341            StatementFact::DropPolicy {
342                name,
343                table,
344                if_exists,
345            } => {
346                mutations.push(Mutation::DropPolicy(DropPolicyMutation {
347                    name: name.clone(),
348                    table: Self::resolve_lookup_name(table, state),
349                    if_exists: *if_exists,
350                }));
351            }
352            StatementFact::CreateTrigger {
353                name,
354                table,
355                function,
356            } => {
357                // Function references in triggers are bare names (e.g., "notify_func")
358                // but functions are stored with signature (e.g., "notify_func()").
359                // Use resolve_function_id_by_sig with empty params for consistent lookup.
360                let function_base = function
361                    .as_ref()
362                    .map(|f| Self::resolve_lookup_name(f, state))
363                    .unwrap_or_else(|| ObjectId::new("public", "unknown_function"));
364                let function_id = Self::resolve_function_id_by_sig(&function_base, "");
365                mutations.push(Mutation::CreateTrigger(CreateTriggerMutation {
366                    name: name.clone(),
367                    table: Self::resolve_lookup_name(table, state),
368                    function_id,
369                }));
370            }
371            StatementFact::DropTrigger {
372                name,
373                table,
374                if_exists,
375            } => {
376                mutations.push(Mutation::DropTrigger(DropTriggerMutation {
377                    name: name.clone(),
378                    table: Self::resolve_lookup_name(table, state),
379                    if_exists: *if_exists,
380                }));
381            }
382            StatementFact::AlterIndex { name, actions } => {
383                let id = Self::resolve_lookup_name(name, state);
384                for action in actions {
385                    match action {
386                        AlterIndexActionFact::RenameTo { new_name } => {
387                            let mut new_id = ObjectId::new(id.schema.clone(), new_name.resolve());
388                            new_id.inferred_schema = id.inferred_schema;
389                            mutations.push(Mutation::Rename(Rename {
390                                old_id: id.clone(),
391                                new_id,
392                            }));
393                        }
394                    }
395                }
396            }
397            StatementFact::CreateType(create_type) => {
398                let id = Self::resolve_creation_name(&create_type.name, state);
399
400                let mapped_kind = match create_type.kind {
401                    TypeCreationKind::Enum => TypeKind::Enum { variants: vec![] },
402                    TypeCreationKind::Range => TypeKind::Range,
403                    TypeCreationKind::Composite => TypeKind::Composite,
404                    TypeCreationKind::Base => TypeKind::Base,
405                };
406
407                mutations.push(Mutation::CreateType(CreateTypeMutation {
408                    id,
409                    kind: mapped_kind,
410                }));
411            }
412            StatementFact::AlterType(alter_type) => {
413                let id = Self::resolve_lookup_name(&alter_type.name, state);
414                for action_fact in &alter_type.actions {
415                    match action_fact {
416                        crate::analysis::facts::AlterTypeActionFact::AddValue { new_value } => {
417                            mutations.push(Mutation::AlterType(AlterTypeMutation {
418                                id: id.clone(),
419                                action: AlterTypeActionMutation::AddValue {
420                                    new_value: new_value.clone(),
421                                },
422                            }));
423                        }
424                    }
425                }
426            }
427            StatementFact::CreateDomain { name, base_type } => {
428                let id = Self::resolve_creation_name(name, state);
429
430                mutations.push(Mutation::CreateDomain(CreateDomainMutation {
431                    id,
432                    base_type: base_type.clone(),
433                }));
434            }
435            StatementFact::AlterDomain { name, action } => {
436                mutations.push(Mutation::AlterDomain(AlterDomainMutation {
437                    id: Self::resolve_lookup_name(name, state),
438                    action: action.clone(),
439                }));
440            }
441            StatementFact::DropDomain {
442                names,
443                if_exists,
444                cascade,
445            } => {
446                let ids = names
447                    .iter()
448                    .map(|n| Self::resolve_lookup_name(n, state))
449                    .collect();
450                mutations.push(Mutation::DropDomain(DropDomainMutation {
451                    ids,
452                    if_exists: *if_exists,
453                    cascade: *cascade,
454                }));
455            }
456            StatementFact::DropType {
457                names,
458                if_exists,
459                cascade,
460            } => {
461                let ids = names
462                    .iter()
463                    .map(|n| Self::resolve_lookup_name(n, state))
464                    .collect();
465                mutations.push(Mutation::DropType(DropTypeMutation {
466                    ids,
467                    if_exists: *if_exists,
468                    cascade: *cascade,
469                }));
470            }
471            StatementFact::CreateSequence {
472                name,
473                if_not_exists,
474                owned_by,
475            } => {
476                let id = Self::resolve_creation_name(name, state);
477
478                let resolved_owned_by = owned_by.as_ref().map(|(table_name, col)| {
479                    (Self::resolve_lookup_name(table_name, state), col.clone())
480                });
481                mutations.push(Mutation::CreateSequence(CreateSequenceMutation {
482                    id,
483                    if_not_exists: *if_not_exists,
484                    owned_by: resolved_owned_by,
485                }));
486            }
487            StatementFact::AlterSequence { name, owned_by } => {
488                let resolved_owned_by = owned_by.as_ref().map(|(table_name, col)| {
489                    (Self::resolve_lookup_name(table_name, state), col.clone())
490                });
491                mutations.push(Mutation::AlterSequence(AlterSequenceMutation {
492                    id: Self::resolve_lookup_name(name, state),
493                    owned_by: resolved_owned_by,
494                }));
495            }
496            StatementFact::DropSequence {
497                names,
498                if_exists,
499                cascade,
500            } => {
501                let ids = names
502                    .iter()
503                    .map(|n| Self::resolve_lookup_name(n, state))
504                    .collect();
505                mutations.push(Mutation::DropSequence(DropSequenceMutation {
506                    ids,
507                    if_exists: *if_exists,
508                    cascade: *cascade,
509                }));
510            }
511            StatementFact::AlterTable { name, actions } => {
512                let id = Self::resolve_lookup_name(name, state);
513                for action_fact in actions {
514                    let action = match action_fact {
515                        AlterTableActionFact::AddColumn {
516                            name: col_name,
517                            ty,
518                            if_not_exists,
519                            not_null,
520                            default,
521                        } => AlterTableActionMutation::AddColumn {
522                            name: col_name.clone(),
523                            ty: ty.clone(),
524                            if_not_exists: *if_not_exists,
525                            not_null: *not_null,
526                            default: default.clone(),
527                            depends_on: None, // Logic for extraction can be added later if needed
528                        },
529                        AlterTableActionFact::DropColumn {
530                            name: col_name,
531                            if_exists,
532                        } => AlterTableActionMutation::DropColumn {
533                            name: col_name.clone(),
534                            if_exists: *if_exists,
535                        },
536                        AlterTableActionFact::RenameColumn { from, to } => {
537                            AlterTableActionMutation::RenameColumn {
538                                from: from.resolve(),
539                                to: to.resolve(),
540                            }
541                        }
542                        AlterTableActionFact::RenameTo { new_name } => {
543                            let mut new_id = ObjectId::new(id.schema.clone(), new_name.resolve());
544                            new_id.inferred_schema = id.inferred_schema;
545                            mutations.push(Mutation::Rename(Rename {
546                                old_id: id.clone(),
547                                new_id,
548                            }));
549                            continue;
550                        }
551                        AlterTableActionFact::AddForeignKey {
552                            constraint_name,
553                            references,
554                            from_columns,
555                            to_columns,
556                            not_valid,
557                        } => {
558                            let to_table = Self::resolve_lookup_name(references, state);
559                            if !state.relation_is_present(&to_table) {
560                                return vec![Mutation::Opaque(
561                                    OpaqueMutation::UnresolvedReference {
562                                        object_kind: crate::report::violations::ObjectKind::Table,
563                                        object_name: to_table.to_string(),
564                                    },
565                                )];
566                            }
567                            AlterTableActionMutation::AddForeignKey {
568                                constraint_name: constraint_name.clone(),
569                                to_table,
570                                from_columns: from_columns.clone(),
571                                to_columns: to_columns.clone(),
572                                not_valid: *not_valid,
573                            }
574                        }
575                        AlterTableActionFact::AlterConstraint {
576                            name: c_name,
577                            deferrable,
578                        } => AlterTableActionMutation::AlterConstraint {
579                            name: c_name.clone(),
580                            deferrable: *deferrable,
581                        },
582                        AlterTableActionFact::RenameConstraint { old_name, new_name } => {
583                            AlterTableActionMutation::RenameConstraint {
584                                old_name: old_name.clone(),
585                                new_name: new_name.clone(),
586                            }
587                        }
588                        AlterTableActionFact::DropConstraint { name: c_name } => {
589                            AlterTableActionMutation::DropConstraint {
590                                name: c_name.clone(),
591                            }
592                        }
593                        AlterTableActionFact::AddCheckConstraint {
594                            constraint_name,
595                            not_valid,
596                        } => AlterTableActionMutation::AddCheckConstraint {
597                            constraint_name: constraint_name.clone(),
598                            not_valid: *not_valid,
599                        },
600                        AlterTableActionFact::AddUniqueConstraint => {
601                            AlterTableActionMutation::AddUniqueConstraint
602                        }
603                        AlterTableActionFact::AddPrimaryKeyConstraint => {
604                            AlterTableActionMutation::AddPrimaryKeyConstraint
605                        }
606                        AlterTableActionFact::AddExcludeConstraint => {
607                            AlterTableActionMutation::AddExcludeConstraint
608                        }
609                        AlterTableActionFact::SetNotNull { column } => {
610                            AlterTableActionMutation::SetNotNull {
611                                column: column.clone(),
612                            }
613                        }
614                        AlterTableActionFact::DropNotNull { column } => {
615                            AlterTableActionMutation::DropNotNull {
616                                column: column.clone(),
617                            }
618                        }
619                        AlterTableActionFact::SetType {
620                            column,
621                            ty,
622                            has_using,
623                        } => AlterTableActionMutation::SetType {
624                            column: column.clone(),
625                            ty: ty.clone(),
626                            has_using: *has_using,
627                        },
628                        AlterTableActionFact::SetDefault { column, default } => {
629                            AlterTableActionMutation::SetDefault {
630                                column: column.clone(),
631                                default: default.clone(),
632                            }
633                        }
634                        AlterTableActionFact::ValidateConstraint { constraint_name } => {
635                            AlterTableActionMutation::ValidateConstraint {
636                                constraint_name: constraint_name.clone(),
637                            }
638                        }
639                        AlterTableActionFact::AttachPartition { child } => {
640                            let child_id = Self::resolve_lookup_name(child, state);
641
642                            AlterTableActionMutation::AttachPartition { child: child_id }
643                        }
644                        AlterTableActionFact::DetachPartition { child } => {
645                            AlterTableActionMutation::DetachPartition {
646                                child: Self::resolve_lookup_name(child, state),
647                            }
648                        }
649                        AlterTableActionFact::SetStorage { column } => {
650                            AlterTableActionMutation::SetStorage {
651                                column: column.clone(),
652                            }
653                        }
654                        AlterTableActionFact::SetAccessMethod => {
655                            AlterTableActionMutation::SetAccessMethod
656                        }
657                        AlterTableActionFact::DisableTrigger { trigger_name } => {
658                            AlterTableActionMutation::DisableTrigger {
659                                trigger_name: trigger_name.clone(),
660                            }
661                        }
662                        AlterTableActionFact::EnableTrigger { trigger_name } => {
663                            AlterTableActionMutation::EnableTrigger {
664                                trigger_name: trigger_name.clone(),
665                            }
666                        }
667                        AlterTableActionFact::SetExpression { .. }
668                        | AlterTableActionFact::SetOptions { .. }
669                        | AlterTableActionFact::Inherit { .. }
670                        | AlterTableActionFact::NoInherit { .. }
671                        | AlterTableActionFact::ClusterOn { .. }
672                        | AlterTableActionFact::InheritTable { .. }
673                        | AlterTableActionFact::NoInheritTable { .. }
674                        | AlterTableActionFact::MergePartitions { .. }
675                        | AlterTableActionFact::SplitPartition
676                        | AlterTableActionFact::SetSchema { .. }
677                        | AlterTableActionFact::SetTablespace { .. }
678                        | AlterTableActionFact::SetLogged
679                        | AlterTableActionFact::SetUnlogged
680                        | AlterTableActionFact::OwnerTo { .. }
681                        | AlterTableActionFact::ReplicaIdentity { .. }
682                        | AlterTableActionFact::ForceRls
683                        | AlterTableActionFact::EnableRls
684                        | AlterTableActionFact::DisableRls
685                        | AlterTableActionFact::EnableAlwaysTrigger { .. }
686                        | AlterTableActionFact::EnableReplicaTrigger { .. } => {
687                            AlterTableActionMutation::Opaque
688                        }
689                    };
690                    mutations.push(Mutation::AlterTable(AlterTable {
691                        id: id.clone(),
692                        action,
693                    }));
694                }
695            }
696            StatementFact::DropTable {
697                name,
698                if_exists,
699                cascade,
700            } => {
701                let id = Self::resolve_lookup_name(name, state);
702
703                // Still emit a DropTable mutation for rule evaluation (e.g. DriftDetectionRule)
704                // even when the table is not present locally. The state machine will handle
705                // tainting confidence in apply().
706                mutations.push(Mutation::DropTable(DropTable {
707                    id,
708                    if_exists: *if_exists,
709                    cascade: *cascade,
710                }));
711            }
712            StatementFact::DropView {
713                name,
714                if_exists,
715                cascade,
716            } => {
717                mutations.push(Mutation::DropView(DropViewMutation {
718                    ids: vec![Self::resolve_lookup_name(name, state)],
719                    if_exists: *if_exists,
720                    cascade: *cascade,
721                }));
722            }
723            StatementFact::DropMaterializedView {
724                names,
725                if_exists,
726                cascade,
727            } => {
728                let ids = names
729                    .iter()
730                    .map(|n| Self::resolve_lookup_name(n, state))
731                    .collect();
732                mutations.push(Mutation::DropMaterializedView(
733                    DropMaterializedViewMutation {
734                        ids,
735                        if_exists: *if_exists,
736                        cascade: *cascade,
737                    },
738                ));
739            }
740            StatementFact::DropIndex {
741                names,
742                if_exists,
743                concurrently,
744            } => {
745                for name in names {
746                    mutations.push(Mutation::DropIndex(DropIndex {
747                        id: Self::resolve_lookup_name(name, state),
748                        if_exists: *if_exists,
749                        concurrently: *concurrently,
750                    }));
751                }
752            }
753            StatementFact::SetSearchPath { target } => {
754                mutations.push(Mutation::SearchPath(SearchPathChange {
755                    target: target.clone(),
756                }))
757            }
758            StatementFact::BeginTransaction => mutations.push(Mutation::BeginTransaction),
759            StatementFact::CommitTransaction => mutations.push(Mutation::CommitTransaction),
760            StatementFact::RollbackTransaction => mutations.push(Mutation::RollbackTransaction),
761            StatementFact::RollbackToSavepoint { name } => {
762                mutations.push(Mutation::RollbackToSavepoint(RollbackToSavepointMutation {
763                    name: name.clone(),
764                }))
765            }
766            StatementFact::Savepoint { name } => {
767                mutations.push(Mutation::Savepoint(SavepointMutation {
768                    name: name.clone(),
769                }))
770            }
771            StatementFact::ReleaseSavepoint { name } => {
772                mutations.push(Mutation::ReleaseSavepoint(ReleaseSavepointMutation {
773                    name: name.clone(),
774                }))
775            }
776            StatementFact::PrepareTransaction { .. } => {
777                mutations.push(Mutation::Opaque(OpaqueMutation::PrepareTransaction))
778            }
779            StatementFact::SetTransaction => {
780                mutations.push(Mutation::Opaque(OpaqueMutation::SetTransaction))
781            }
782            StatementFact::SetConstraints => {
783                mutations.push(Mutation::Opaque(OpaqueMutation::SetConstraints))
784            }
785            StatementFact::OpaqueBlock => mutations.push(Mutation::Opaque(OpaqueMutation::DoBlock)),
786            StatementFact::Execute => mutations.push(Mutation::Opaque(OpaqueMutation::Execute)),
787            StatementFact::Vacuum { relation, is_full } => {
788                let table_id = relation
789                    .as_ref()
790                    .map(|r| Self::resolve_lookup_name(r, state));
791                mutations.push(Mutation::Vacuum {
792                    table_id,
793                    is_full: *is_full,
794                })
795            }
796            StatementFact::CreateFunction(f) => {
797                let id = Self::resolve_function_id(&f.name, &f.params, state);
798                mutations.push(Mutation::CreateFunction(CreateFunctionMutation {
799                    id,
800                    or_replace: f.or_replace,
801                    params: f.params.clone(),
802                    return_type: f.return_type.clone(),
803                    options: f.options.clone(),
804                }));
805            }
806            StatementFact::AlterFunction(f) => {
807                let base_id = Self::resolve_lookup_name(&f.name, state);
808                let sig = f.params.join(",");
809                let id = Self::resolve_function_id_by_sig(&base_id, &sig);
810                mutations.push(Mutation::AlterFunction(AlterFunctionMutation {
811                    id,
812                    action: f.action.clone(),
813                }));
814            }
815            StatementFact::DropFunction(f) => {
816                let mut signatures = Vec::new();
817                for sig in &f.signatures {
818                    let mut normalized_sig = sig.clone();
819                    normalized_sig.params = normalized_sig
820                        .params
821                        .into_iter()
822                        .map(|p| {
823                            let p = p.trim().to_lowercase();
824                            match p.as_str() {
825                                "int" | "int4" => "integer".to_string(),
826                                "int8" => "bigint".to_string(),
827                                "int2" => "smallint".to_string(),
828                                "float8" => "double precision".to_string(),
829                                "float4" => "real".to_string(),
830                                "bool" => "boolean".to_string(),
831                                "varchar" => "character varying".to_string(),
832                                "char" => "character".to_string(),
833                                "time" => "time without time zone".to_string(),
834                                "timestamp" => "timestamp without time zone".to_string(),
835                                "timestamptz" => "timestamp with time zone".to_string(),
836                                "decimal" => "numeric".to_string(),
837                                _ => p,
838                            }
839                        })
840                        .collect();
841                    signatures.push(normalized_sig);
842                }
843                mutations.push(Mutation::DropFunction(DropFunctionMutation {
844                    signatures,
845                    if_exists: f.if_exists,
846                    cascade: f.cascade,
847                }));
848            }
849            StatementFact::CreateProcedure(p) => {
850                let id = Self::resolve_function_id(&p.name, &p.params, state);
851                mutations.push(Mutation::CreateProcedure(CreateProcedureMutation {
852                    id,
853                    or_replace: p.or_replace,
854                    params: p.params.clone(),
855                    options: p.options.clone(),
856                }));
857            }
858            StatementFact::AlterProcedure(p) => {
859                let base_id = Self::resolve_lookup_name(&p.name, state);
860                let sig = p
861                    .params
862                    .iter()
863                    .map(|p| p.to_string())
864                    .collect::<Vec<_>>()
865                    .join(",");
866                let id = Self::resolve_function_id_by_sig(&base_id, &sig);
867                mutations.push(Mutation::AlterProcedure(AlterProcedureMutation {
868                    id,
869                    action: p.action.clone(),
870                }));
871            }
872            StatementFact::DropProcedure(p) => {
873                mutations.push(Mutation::DropProcedure(DropProcedureMutation {
874                    signatures: p.signatures.clone(),
875                    if_exists: p.if_exists,
876                    cascade: p.cascade,
877                }));
878            }
879            StatementFact::CreatePublication(p) => {
880                mutations.push(Mutation::CreatePublication(CreatePublicationMutation {
881                    name: p.name.clone(),
882                    scope: p.scope.clone(),
883                    params: p.params.clone(),
884                }));
885            }
886            StatementFact::AlterPublication(p) => {
887                mutations.push(Mutation::AlterPublication(AlterPublicationMutation {
888                    name: p.name.clone(),
889                }));
890            }
891            StatementFact::DropPublication(p) => {
892                mutations.push(Mutation::DropPublication(DropPublicationMutation {
893                    names: p.names.clone(),
894                    if_exists: p.if_exists,
895                    cascade: p.cascade,
896                }));
897            }
898            StatementFact::CreateSubscription(s) => {
899                mutations.push(Mutation::CreateSubscription(CreateSubscriptionMutation {
900                    name: s.name.clone(),
901                    connection: s.connection.clone(),
902                    publications: s.publications.clone(),
903                    params: s.params.clone(),
904                }));
905            }
906            StatementFact::AlterSubscription(s) => {
907                mutations.push(Mutation::AlterSubscription(AlterSubscriptionMutation {
908                    name: s.name.clone(),
909                }));
910            }
911            StatementFact::DropSubscription(s) => {
912                mutations.push(Mutation::DropSubscription(DropSubscriptionMutation {
913                    name: s.name.clone(),
914                    if_exists: s.if_exists,
915                }));
916            }
917            StatementFact::CreateRole(r) => {
918                mutations.push(Mutation::CreateRole(CreateRoleMutation {
919                    name: r.name.clone(),
920                    inherits: r.inherits,
921                }));
922            }
923            StatementFact::AlterRole(r) => {
924                mutations.push(Mutation::AlterRole(AlterRoleMutation {
925                    name: r.name.clone(),
926                    inherits: r.inherits,
927                }));
928            }
929            StatementFact::DropRole(r) => {
930                mutations.push(Mutation::DropRole(DropRoleMutation {
931                    names: r.names.clone(),
932                    if_exists: r.if_exists,
933                }));
934            }
935            StatementFact::Grant(g) => {
936                mutations.push(Mutation::Grant(GrantMutation {
937                    privileges: g.privileges.clone(),
938                    target: Self::resolve_grant_target(&g.target, state),
939                    grantees: g.grantees.clone(),
940                    with_grant_option: g.with_grant_option,
941                    granted_by: g.granted_by.clone(),
942                }));
943            }
944            StatementFact::Revoke(r) => {
945                mutations.push(Mutation::Revoke(RevokeMutation {
946                    grant_option_only: r.grant_option_only,
947                    privileges: r.privileges.clone(),
948                    target: Self::resolve_grant_target(&r.target, state),
949                    revokees: r.revokees.clone(),
950                    granted_by: r.granted_by.clone(),
951                    cascade: r.cascade,
952                }));
953            }
954            StatementFact::CreateDatabase(d) => {
955                mutations.push(Mutation::CreateDatabase(CreateDatabaseMutation {
956                    name: d.name.clone(),
957                    options: d.options.clone(),
958                }));
959            }
960            StatementFact::AlterDatabase(d) => {
961                let id = Self::resolve_lookup_name(&d.name, state);
962                mutations.push(Mutation::AlterDatabase(AlterDatabaseMutation {
963                    id,
964                    action: d.action.clone(),
965                }));
966            }
967            StatementFact::DropDatabase(d) => {
968                let id = Self::resolve_lookup_name(&d.name, state);
969                mutations.push(Mutation::DropDatabase(DropDatabaseMutation {
970                    id,
971                    if_exists: d.if_exists,
972                }));
973            }
974        }
975        mutations
976    }
977}