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