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    AlterDomainMutation, AlterSequenceMutation, AlterTable, AlterTableActionMutation,
7    AlterTypeActionMutation, AlterTypeMutation, ColumnMutation, CreateDomainMutation, CreateIndex,
8    CreateMaterializedView, CreatePolicyMutation, CreateSchemaMutation, CreateSequenceMutation,
9    CreateTable, CreateTriggerMutation, CreateTypeMutation, CreateView, DropDomainMutation,
10    DropIndex, DropMaterializedViewMutation, DropPolicyMutation, DropSchemaMutation,
11    DropSequenceMutation, DropTable, DropTriggerMutation, DropViewMutation, FkMutation, Mutation,
12    OpaqueMutation, PersistenceMutation, RefreshMaterializedViewMutation, ReleaseSavepointMutation,
13    Rename, RollbackToSavepointMutation, SavepointMutation, SearchPathChange,
14};
15use crate::analysis::state::AnalysisState;
16use crate::ast::identifiers::{ObjectId, QualifiedName};
17use crate::model::types::TypeKind;
18
19pub struct Resolver;
20
21impl Resolver {
22    fn resolve_creation_name(name: &QualifiedName, state: &AnalysisState) -> ObjectId {
23        let schema = name
24            .schema
25            .as_ref()
26            .map(|i| i.resolve())
27            .unwrap_or_else(|| {
28                state
29                    .local
30                    .search_path
31                    .first()
32                    .map(|s| s.as_str())
33                    .unwrap_or("public")
34                    .to_string()
35            });
36
37        ObjectId {
38            schema,
39            name: name.name.resolve(),
40        }
41    }
42
43    fn resolve_lookup_name(name: &QualifiedName, state: &AnalysisState) -> ObjectId {
44        if let Some(schema_ident) = &name.schema {
45            return ObjectId {
46                schema: schema_ident.resolve(),
47                name: name.name.resolve(),
48            };
49        }
50
51        let resolved_name = name.name.resolve();
52
53        for schema in &state.local.search_path {
54            let candidate = ObjectId {
55                schema: schema.clone(),
56                name: resolved_name.clone(),
57            };
58            if state.local.relations.contains_key(&candidate)
59                || state.local.types.contains_key(&candidate)
60                || state.local.sequences.contains_key(&candidate)
61            {
62                return candidate;
63            }
64        }
65
66        let schema = state
67            .local
68            .search_path
69            .first()
70            .map(|s| s.as_str())
71            .unwrap_or("public")
72            .to_string();
73        ObjectId {
74            schema,
75            name: resolved_name,
76        }
77    }
78
79    pub fn resolve(fact: &StatementFact, state: &AnalysisState) -> Vec<Mutation> {
80        let mut mutations = Vec::new();
81        match fact {
82            StatementFact::CreateSchema {
83                name,
84                if_not_exists,
85            } => {
86                mutations.push(Mutation::CreateSchema(CreateSchemaMutation {
87                    name: name.name.resolve(),
88                    if_not_exists: *if_not_exists,
89                }));
90            }
91            StatementFact::AlterSchema { .. } => {
92                mutations.push(Mutation::Opaque(OpaqueMutation::DynamicSql));
93            }
94            StatementFact::DropSchema {
95                names,
96                if_exists,
97                cascade,
98            } => {
99                mutations.push(Mutation::DropSchema(DropSchemaMutation {
100                    names: names.iter().map(|n| n.name.resolve()).collect(),
101                    if_exists: *if_exists,
102                    cascade: *cascade,
103                }));
104            }
105            StatementFact::CreateTable {
106                name,
107                if_not_exists,
108                as_select,
109                persistence,
110                columns,
111                foreign_keys,
112                table_constraints,
113                partition_by,
114                partition_of,
115            } => {
116                let id = Self::resolve_creation_name(name, state);
117
118                if !*if_not_exists
119                    && (state.relation_is_present(&id)
120                        || matches!(
121                            state.local.types.get(&id),
122                            Some(crate::model::types::TypeOverlay::Present(_))
123                        )
124                        || matches!(
125                            state.local.sequences.get(&id),
126                            Some(crate::model::sequence::SequenceOverlay::Present(_))
127                        ))
128                {
129                    return vec![Mutation::Opaque(OpaqueMutation::DynamicSql)];
130                }
131
132                let resolved_persistence = match persistence {
133                    PersistenceFact::Permanent => PersistenceMutation::Permanent,
134                    PersistenceFact::Temporary => PersistenceMutation::Temporary,
135                    PersistenceFact::Unlogged => PersistenceMutation::Unlogged,
136                };
137
138                let col_mutations: Vec<ColumnMutation> = columns
139                    .iter()
140                    .map(|c| ColumnMutation {
141                        name: c.name.clone(),
142                        ty: c.ty.clone(),
143                        not_null: c.not_null,
144                        is_primary_key: c.is_primary_key,
145                        default: c.default.clone(),
146                    })
147                    .collect();
148
149                let mut fk_mutations = Vec::new();
150                for fk in foreign_keys {
151                    let to_table = Self::resolve_lookup_name(&fk.references, state);
152                    if !state.relation_is_present(&to_table) {
153                        return vec![Mutation::Opaque(OpaqueMutation::DynamicSql)];
154                    }
155                    fk_mutations.push(FkMutation {
156                        constraint_name: fk.constraint_name.clone(),
157                        to_table,
158                        from_columns: fk.from_columns.clone(),
159                        to_columns: fk.to_columns.clone(),
160                    });
161                }
162
163                let partition_of_id = partition_of
164                    .as_ref()
165                    .map(|n| Self::resolve_lookup_name(n, state));
166
167                if let Some(p_id) = &partition_of_id
168                    && !state.relation_is_present(p_id)
169                {
170                    return vec![Mutation::Opaque(OpaqueMutation::DynamicSql)];
171                }
172
173                mutations.push(Mutation::CreateTable(CreateTable {
174                    id,
175                    if_not_exists: *if_not_exists,
176                    as_select: *as_select,
177                    persistence: resolved_persistence,
178                    columns: col_mutations,
179                    foreign_keys: fk_mutations,
180                    table_constraints: table_constraints.clone(),
181                    partition_by: partition_by.clone(),
182                    partition_of: partition_of_id,
183                }));
184            }
185            StatementFact::CreateView {
186                name,
187                or_replace,
188                depends_on,
189            } => {
190                let id = Self::resolve_creation_name(name, state);
191
192                if !*or_replace && state.relation_is_present(&id) {
193                    return vec![Mutation::Opaque(OpaqueMutation::DynamicSql)];
194                }
195
196                let resolved_depends = depends_on
197                    .iter()
198                    .map(|n| Self::resolve_lookup_name(n, state))
199                    .collect();
200
201                mutations.push(Mutation::CreateView(CreateView {
202                    id,
203                    or_replace: *or_replace,
204                    depends_on: resolved_depends,
205                }));
206            }
207            StatementFact::AlterView { name, new_name } => {
208                if let Some(new_name) = new_name {
209                    let id = Self::resolve_lookup_name(name, state);
210                    let new_id = ObjectId {
211                        schema: id.schema.clone(),
212                        name: new_name.resolve(),
213                    };
214                    mutations.push(Mutation::Rename(Rename { old_id: id, new_id }));
215                }
216            }
217            StatementFact::CreateMaterializedView { name, depends_on } => {
218                let id = Self::resolve_creation_name(name, state);
219
220                if state.relation_is_present(&id) {
221                    return vec![Mutation::Opaque(OpaqueMutation::DynamicSql)];
222                }
223
224                let resolved_depends = depends_on
225                    .iter()
226                    .map(|n| Self::resolve_lookup_name(n, state))
227                    .collect();
228
229                mutations.push(Mutation::CreateMaterializedView(CreateMaterializedView {
230                    id,
231                    depends_on: resolved_depends,
232                }));
233            }
234            StatementFact::AlterMaterializedView { name, new_name } => {
235                if let Some(new_name) = new_name {
236                    let id = Self::resolve_lookup_name(name, state);
237                    let new_id = ObjectId {
238                        schema: id.schema.clone(),
239                        name: new_name.resolve(),
240                    };
241                    mutations.push(Mutation::Rename(Rename { old_id: id, new_id }));
242                }
243            }
244            StatementFact::RefreshMaterializedView { name, concurrently } => {
245                mutations.push(Mutation::RefreshMaterializedView(
246                    RefreshMaterializedViewMutation {
247                        id: Self::resolve_lookup_name(name, state),
248                        concurrently: *concurrently,
249                    },
250                ));
251            }
252            StatementFact::CreateIndex {
253                name,
254                relation,
255                if_not_exists,
256                concurrently,
257                using_method,
258                has_predicate,
259            } => {
260                let id = Self::resolve_creation_name(name, state);
261
262                if !*if_not_exists && state.local.graph.indexes.iter().any(|ix| ix.index_id == id) {
263                    return vec![Mutation::Opaque(OpaqueMutation::DynamicSql)];
264                }
265
266                mutations.push(Mutation::CreateIndex(CreateIndex {
267                    id,
268                    table: Self::resolve_lookup_name(relation, state),
269                    if_not_exists: *if_not_exists,
270                    concurrently: *concurrently,
271                    using_method: using_method.clone(),
272                    has_predicate: *has_predicate,
273                }));
274            }
275            StatementFact::CreatePolicy { name, table } => {
276                mutations.push(Mutation::CreatePolicy(CreatePolicyMutation {
277                    name: name.clone(),
278                    table: Self::resolve_lookup_name(table, state),
279                }));
280            }
281            StatementFact::DropPolicy {
282                name,
283                table,
284                if_exists,
285            } => {
286                mutations.push(Mutation::DropPolicy(DropPolicyMutation {
287                    name: name.clone(),
288                    table: Self::resolve_lookup_name(table, state),
289                    if_exists: *if_exists,
290                }));
291            }
292            StatementFact::CreateTrigger { name, table } => {
293                mutations.push(Mutation::CreateTrigger(CreateTriggerMutation {
294                    name: name.clone(),
295                    table: Self::resolve_lookup_name(table, state),
296                }));
297            }
298            StatementFact::DropTrigger {
299                name,
300                table,
301                if_exists,
302            } => {
303                mutations.push(Mutation::DropTrigger(DropTriggerMutation {
304                    name: name.clone(),
305                    table: Self::resolve_lookup_name(table, state),
306                    if_exists: *if_exists,
307                }));
308            }
309            StatementFact::AlterIndex { name, actions } => {
310                let id = Self::resolve_lookup_name(name, state);
311                for action in actions {
312                    match action {
313                        AlterIndexActionFact::RenameTo { new_name } => {
314                            let new_id = ObjectId {
315                                schema: id.schema.clone(),
316                                name: new_name.resolve(),
317                            };
318                            mutations.push(Mutation::Rename(Rename {
319                                old_id: id.clone(),
320                                new_id,
321                            }));
322                        }
323                    }
324                }
325            }
326            StatementFact::CreateType(create_type) => {
327                let id = Self::resolve_creation_name(&create_type.name, state);
328
329                if matches!(
330                    state.local.types.get(&id),
331                    Some(crate::model::types::TypeOverlay::Present(_))
332                ) || state.relation_is_present(&id)
333                {
334                    return vec![Mutation::Opaque(OpaqueMutation::DynamicSql)];
335                }
336
337                let mapped_kind = match create_type.kind {
338                    TypeCreationKind::Enum => TypeKind::Enum { variants: vec![] },
339                    TypeCreationKind::Range => TypeKind::Range,
340                    TypeCreationKind::Composite => TypeKind::Composite,
341                    TypeCreationKind::Base => TypeKind::Base,
342                };
343
344                mutations.push(Mutation::CreateType(CreateTypeMutation {
345                    id,
346                    kind: mapped_kind,
347                }));
348            }
349            StatementFact::AlterType(alter_type) => {
350                let id = Self::resolve_lookup_name(&alter_type.name, state);
351                for action_fact in &alter_type.actions {
352                    match action_fact {
353                        crate::analysis::facts::AlterTypeActionFact::AddValue { new_value } => {
354                            mutations.push(Mutation::AlterType(AlterTypeMutation {
355                                id: id.clone(),
356                                action: AlterTypeActionMutation::AddValue {
357                                    new_value: new_value.clone(),
358                                },
359                            }));
360                        }
361                    }
362                }
363            }
364            StatementFact::CreateDomain { name, base_type } => {
365                let id = Self::resolve_creation_name(name, state);
366
367                if matches!(
368                    state.local.types.get(&id),
369                    Some(crate::model::types::TypeOverlay::Present(_))
370                ) || state.relation_is_present(&id)
371                {
372                    return vec![Mutation::Opaque(OpaqueMutation::DynamicSql)];
373                }
374
375                mutations.push(Mutation::CreateDomain(CreateDomainMutation {
376                    id,
377                    base_type: base_type.clone(),
378                }));
379            }
380            StatementFact::AlterDomain { name } => {
381                mutations.push(Mutation::AlterDomain(AlterDomainMutation {
382                    id: Self::resolve_lookup_name(name, state),
383                }));
384            }
385            StatementFact::DropDomain { names, if_exists } => {
386                let ids = names
387                    .iter()
388                    .map(|n| Self::resolve_lookup_name(n, state))
389                    .collect();
390                mutations.push(Mutation::DropDomain(DropDomainMutation {
391                    ids,
392                    if_exists: *if_exists,
393                }));
394            }
395            StatementFact::CreateSequence {
396                name,
397                if_not_exists,
398                owned_by,
399            } => {
400                let id = Self::resolve_creation_name(name, state);
401
402                if !*if_not_exists
403                    && matches!(
404                        state.local.sequences.get(&id),
405                        Some(crate::model::sequence::SequenceOverlay::Present(_))
406                    )
407                {
408                    return vec![Mutation::Opaque(OpaqueMutation::DynamicSql)];
409                }
410
411                let resolved_owned_by = owned_by.as_ref().map(|(table_name, col)| {
412                    (Self::resolve_lookup_name(table_name, state), col.clone())
413                });
414                mutations.push(Mutation::CreateSequence(CreateSequenceMutation {
415                    id,
416                    if_not_exists: *if_not_exists,
417                    owned_by: resolved_owned_by,
418                }));
419            }
420            StatementFact::AlterSequence { name, owned_by } => {
421                let resolved_owned_by = owned_by.as_ref().map(|(table_name, col)| {
422                    (Self::resolve_lookup_name(table_name, state), col.clone())
423                });
424                mutations.push(Mutation::AlterSequence(AlterSequenceMutation {
425                    id: Self::resolve_lookup_name(name, state),
426                    owned_by: resolved_owned_by,
427                }));
428            }
429            StatementFact::DropSequence { names, if_exists } => {
430                let ids = names
431                    .iter()
432                    .map(|n| Self::resolve_lookup_name(n, state))
433                    .collect();
434                mutations.push(Mutation::DropSequence(DropSequenceMutation {
435                    ids,
436                    if_exists: *if_exists,
437                }));
438            }
439            StatementFact::AlterTable { name, actions } => {
440                let id = Self::resolve_lookup_name(name, state);
441                for action_fact in actions {
442                    let action = match action_fact {
443                        AlterTableActionFact::AddColumn {
444                            name: col_name,
445                            ty,
446                            if_not_exists,
447                            not_null,
448                            default,
449                        } => AlterTableActionMutation::AddColumn {
450                            name: col_name.clone(),
451                            ty: ty.clone(),
452                            if_not_exists: *if_not_exists,
453                            not_null: *not_null,
454                            default: default.clone(),
455                        },
456                        AlterTableActionFact::DropColumn {
457                            name: col_name,
458                            if_exists,
459                        } => AlterTableActionMutation::DropColumn {
460                            name: col_name.clone(),
461                            if_exists: *if_exists,
462                        },
463                        AlterTableActionFact::RenameColumn { from, to } => {
464                            AlterTableActionMutation::RenameColumn {
465                                from: from.resolve(),
466                                to: to.resolve(),
467                            }
468                        }
469                        AlterTableActionFact::RenameTo { new_name } => {
470                            let new_id = ObjectId {
471                                schema: id.schema.clone(),
472                                name: new_name.resolve(),
473                            };
474                            mutations.push(Mutation::Rename(Rename {
475                                old_id: id.clone(),
476                                new_id,
477                            }));
478                            continue;
479                        }
480                        AlterTableActionFact::AddForeignKey {
481                            constraint_name,
482                            references,
483                            from_columns,
484                            to_columns,
485                            not_valid,
486                        } => {
487                            let to_table = Self::resolve_lookup_name(references, state);
488                            if !state.relation_is_present(&to_table) {
489                                return vec![Mutation::Opaque(OpaqueMutation::DynamicSql)];
490                            }
491                            AlterTableActionMutation::AddForeignKey {
492                                constraint_name: constraint_name.clone(),
493                                to_table,
494                                from_columns: from_columns.clone(),
495                                to_columns: to_columns.clone(),
496                                not_valid: *not_valid,
497                            }
498                        }
499                        AlterTableActionFact::AlterConstraint {
500                            name: c_name,
501                            deferrable,
502                        } => AlterTableActionMutation::AlterConstraint {
503                            name: c_name.clone(),
504                            deferrable: *deferrable,
505                        },
506                        AlterTableActionFact::RenameConstraint { old_name, new_name } => {
507                            AlterTableActionMutation::RenameConstraint {
508                                old_name: old_name.clone(),
509                                new_name: new_name.clone(),
510                            }
511                        }
512                        AlterTableActionFact::DropConstraint { name: c_name } => {
513                            AlterTableActionMutation::DropConstraint {
514                                name: c_name.clone(),
515                            }
516                        }
517                        AlterTableActionFact::AddCheckConstraint {
518                            constraint_name,
519                            not_valid,
520                        } => AlterTableActionMutation::AddCheckConstraint {
521                            constraint_name: constraint_name.clone(),
522                            not_valid: *not_valid,
523                        },
524                        AlterTableActionFact::AddUniqueConstraint => {
525                            AlterTableActionMutation::AddUniqueConstraint
526                        }
527                        AlterTableActionFact::AddPrimaryKeyConstraint => {
528                            AlterTableActionMutation::AddPrimaryKeyConstraint
529                        }
530                        AlterTableActionFact::AddExcludeConstraint => {
531                            AlterTableActionMutation::AddExcludeConstraint
532                        }
533                        AlterTableActionFact::SetNotNull { column } => {
534                            AlterTableActionMutation::SetNotNull {
535                                column: column.clone(),
536                            }
537                        }
538                        AlterTableActionFact::DropNotNull { column } => {
539                            AlterTableActionMutation::DropNotNull {
540                                column: column.clone(),
541                            }
542                        }
543                        AlterTableActionFact::SetType {
544                            column,
545                            ty,
546                            has_using,
547                        } => AlterTableActionMutation::SetType {
548                            column: column.clone(),
549                            ty: ty.clone(),
550                            has_using: *has_using,
551                        },
552                        AlterTableActionFact::SetDefault { column, default } => {
553                            AlterTableActionMutation::SetDefault {
554                                column: column.clone(),
555                                default: default.clone(),
556                            }
557                        }
558                        AlterTableActionFact::ValidateConstraint { constraint_name } => {
559                            AlterTableActionMutation::ValidateConstraint {
560                                constraint_name: constraint_name.clone(),
561                            }
562                        }
563                        AlterTableActionFact::AttachPartition { child } => {
564                            let child_id = Self::resolve_lookup_name(child, state);
565                            if state.local.graph.check_partition_cycle(&id, &child_id) {
566                                return vec![Mutation::Opaque(OpaqueMutation::DynamicSql)];
567                            }
568                            AlterTableActionMutation::AttachPartition { child: child_id }
569                        }
570                        AlterTableActionFact::DetachPartition { child } => {
571                            AlterTableActionMutation::DetachPartition {
572                                child: Self::resolve_lookup_name(child, state),
573                            }
574                        }
575                        AlterTableActionFact::SetStorage { column } => {
576                            AlterTableActionMutation::SetStorage {
577                                column: column.clone(),
578                            }
579                        }
580                        AlterTableActionFact::SetAccessMethod => {
581                            AlterTableActionMutation::SetAccessMethod
582                        }
583                    };
584                    mutations.push(Mutation::AlterTable(AlterTable {
585                        id: id.clone(),
586                        action,
587                    }));
588                }
589            }
590            StatementFact::DropTable {
591                name,
592                if_exists,
593                cascade,
594            } => {
595                mutations.push(Mutation::DropTable(DropTable {
596                    id: Self::resolve_lookup_name(name, state),
597                    if_exists: *if_exists,
598                    cascade: *cascade,
599                }));
600            }
601            StatementFact::DropView { names, if_exists } => {
602                let ids = names
603                    .iter()
604                    .map(|n| Self::resolve_lookup_name(n, state))
605                    .collect();
606                mutations.push(Mutation::DropView(DropViewMutation {
607                    ids,
608                    if_exists: *if_exists,
609                }));
610            }
611            StatementFact::DropMaterializedView { names, if_exists } => {
612                let ids = names
613                    .iter()
614                    .map(|n| Self::resolve_lookup_name(n, state))
615                    .collect();
616                mutations.push(Mutation::DropMaterializedView(
617                    DropMaterializedViewMutation {
618                        ids,
619                        if_exists: *if_exists,
620                    },
621                ));
622            }
623            StatementFact::DropIndex {
624                names,
625                if_exists,
626                concurrently,
627            } => {
628                for name in names {
629                    mutations.push(Mutation::DropIndex(DropIndex {
630                        id: Self::resolve_lookup_name(name, state),
631                        if_exists: *if_exists,
632                        concurrently: *concurrently,
633                    }));
634                }
635            }
636            StatementFact::SetSearchPath { target } => {
637                mutations.push(Mutation::SearchPath(SearchPathChange {
638                    target: target.clone(),
639                }))
640            }
641            StatementFact::BeginTransaction => mutations.push(Mutation::BeginTransaction),
642            StatementFact::CommitTransaction => mutations.push(Mutation::CommitTransaction),
643            StatementFact::RollbackTransaction => mutations.push(Mutation::RollbackTransaction),
644            StatementFact::RollbackToSavepoint { name } => {
645                if !state.local.transactions.iter().any(|t| t.name == *name) {
646                    return vec![Mutation::Opaque(OpaqueMutation::DynamicSql)];
647                }
648                mutations.push(Mutation::RollbackToSavepoint(RollbackToSavepointMutation {
649                    name: name.clone(),
650                }))
651            }
652            StatementFact::Savepoint { name } => {
653                mutations.push(Mutation::Savepoint(SavepointMutation {
654                    name: name.clone(),
655                }))
656            }
657            StatementFact::ReleaseSavepoint { name } => {
658                if !state.local.transactions.iter().any(|t| t.name == *name) {
659                    return vec![Mutation::Opaque(OpaqueMutation::DynamicSql)];
660                }
661                mutations.push(Mutation::ReleaseSavepoint(ReleaseSavepointMutation {
662                    name: name.clone(),
663                }))
664            }
665            StatementFact::PrepareTransaction { .. } => {
666                mutations.push(Mutation::Opaque(OpaqueMutation::PrepareTransaction))
667            }
668            StatementFact::SetTransaction => {
669                mutations.push(Mutation::Opaque(OpaqueMutation::SetTransaction))
670            }
671            StatementFact::SetConstraints => {
672                mutations.push(Mutation::Opaque(OpaqueMutation::SetConstraints))
673            }
674            StatementFact::OpaqueBlock => mutations.push(Mutation::Opaque(OpaqueMutation::DoBlock)),
675            StatementFact::Execute => mutations.push(Mutation::Opaque(OpaqueMutation::Execute)),
676            StatementFact::Vacuum { is_full } => {
677                mutations.push(Mutation::Vacuum { is_full: *is_full })
678            }
679        }
680        mutations
681    }
682}