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