Skip to main content

safe_migrate/analysis/
resolver.rs

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