Skip to main content

reshape/migrations/
remove_foreign_key.rs

1use super::{Action, MigrationContext};
2use crate::{
3    db::{Conn, Transaction},
4    schema::Schema,
5};
6use anyhow::{anyhow, Context};
7use serde::{Deserialize, Serialize};
8
9#[derive(Serialize, Deserialize, Debug)]
10pub struct RemoveForeignKey {
11    table: String,
12    foreign_key: String,
13}
14
15#[typetag::serde(name = "remove_foreign_key")]
16impl Action for RemoveForeignKey {
17    fn describe(&self) -> String {
18        format!(
19            "Removing foreign key \"{}\" from table \"{}\"",
20            self.foreign_key, self.table
21        )
22    }
23
24    fn run(
25        &self,
26        _ctx: &MigrationContext,
27        db: &mut dyn Conn,
28        schema: &Schema,
29    ) -> anyhow::Result<()> {
30        // The foreign key is only removed once the migration is completed.
31        // Removing it earlier would be hard/undesirable for several reasons:
32        // - Postgres doesn't have an easy way to temporarily disable a foreign key check.
33        //   If it did, we could disable the FK for the new schema.
34        // - Even if we could, it probably wouldn't be a good idea as it would cause temporary
35        //   inconsistencies for the old schema which still expects the FK to hold.
36        // - For the same reason, we can't remove the FK when the migration is first applied.
37        //   If the migration was to be aborted, then the FK would have to be recreated with
38        //   the risk that it would no longer be valid.
39
40        // Ensure foreign key exists
41        let table = schema.get_table(db, &self.table)?;
42        let fk_exists = !db
43            .query(&format!(
44                r#"
45                SELECT constraint_name
46                FROM information_schema.table_constraints
47                WHERE
48                    constraint_type = 'FOREIGN KEY' AND
49                    table_name = '{table_name}' AND
50                    constraint_name = '{foreign_key}' 
51                "#,
52                table_name = table.real_name,
53                foreign_key = self.foreign_key,
54            ))
55            .context("failed to check for foreign key")?
56            .is_empty();
57
58        if !fk_exists {
59            return Err(anyhow!(
60                "no foreign key \"{}\" exists on table \"{}\"",
61                self.foreign_key,
62                self.table
63            ));
64        }
65
66        Ok(())
67    }
68
69    fn complete<'a>(
70        &self,
71        _ctx: &MigrationContext,
72        db: &'a mut dyn Conn,
73    ) -> anyhow::Result<Option<Transaction<'a>>> {
74        db.run(&format!(
75            r#"
76            ALTER TABLE {table}
77            DROP CONSTRAINT IF EXISTS {foreign_key}
78            "#,
79            table = self.table,
80            foreign_key = self.foreign_key,
81        ))
82        .context("failed to remove foreign key")?;
83        Ok(None)
84    }
85
86    fn update_schema(&self, _ctx: &MigrationContext, _schema: &mut Schema) {}
87
88    fn abort(&self, _ctx: &MigrationContext, _db: &mut dyn Conn) -> anyhow::Result<()> {
89        Ok(())
90    }
91}