1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
use super::adb::{ATable, DeferredSqlType, TypeKey, ADB};
use super::ButaneMigration;
use crate::db::ConnectionMethods;
use crate::query::{BoolExpr, Expr};
use crate::{db, sqlval::ToSql, DataObject, DataResult, Error, Result};
use std::borrow::Cow;
use std::cmp::PartialEq;

/// Type representing a database migration. A migration describes how
/// to bring the database from state A to state B. In general, the
/// methods on this type are persistent -- they read from and write to
/// the filesystem.
///
/// A Migration cannot be constructed directly, only retrieved from
/// [Migrations][crate::migrations::Migrations].
pub trait Migration: PartialEq {
    /// Retrieves the full abstract database state describing all tables
    fn db(&self) -> Result<ADB>;

    /// Get the name of the migration before this one (if any).
    fn migration_from(&self) -> Result<Option<Cow<str>>>
    where
        Self: Sized;

    /// The name of this migration.
    fn name(&self) -> Cow<str>;

    /// The backend-specific commands to apply this migration.
    fn up_sql(&self, backend_name: &str) -> Result<Option<String>>;

    /// The backend-specific commands to undo this migration.
    fn down_sql(&self, backend_name: &str) -> Result<Option<String>>;

    /// The names of the backends this migration has sql for.
    fn sql_backends(&self) -> Result<Vec<String>>;

    /// Apply the migration to a database connection. The connection
    /// must be for the same type of database as this and the database
    /// must be in the state of the migration prior to this one
    fn apply(&self, conn: &mut impl db::BackendConnection) -> Result<()> {
        let backend_name = conn.backend_name();
        let tx = conn.transaction()?;
        let sql = self
            .up_sql(backend_name)?
            .ok_or_else(|| Error::UnknownBackend(backend_name.to_string()))?;
        tx.execute(&sql)?;
        self.mark_applied(&tx)?;
        tx.commit()
    }

    /// Mark the migration as being applied without doing any
    /// work. Use carefully -- the caller must ensure that the
    /// database schema already matches that expected by this
    /// migration.
    fn mark_applied(&self, conn: &impl db::ConnectionMethods) -> Result<()> {
        conn.insert_only(
            ButaneMigration::TABLE,
            ButaneMigration::COLUMNS,
            &[self.name().as_ref().to_sql_ref()],
        )
    }

    /// Un-apply (downgrade) the migration to a database
    /// connection. The connection must be for the same type of
    /// database as this and this must be the latest migration applied
    /// to the database.
    fn downgrade(&self, conn: &mut impl db::BackendConnection) -> Result<()> {
        let backend_name = conn.backend_name();
        let tx = conn.transaction()?;
        let sql = self
            .down_sql(backend_name)?
            .ok_or_else(|| Error::UnknownBackend(backend_name.to_string()))?;
        tx.execute(&sql)?;
        let nameval = self.name().as_ref().to_sql();
        tx.delete_where(
            ButaneMigration::TABLE,
            BoolExpr::Eq(ButaneMigration::PKCOL, Expr::Val(nameval)),
        )?;
        tx.commit()
    }
}

/// A migration which can be modified
pub trait MigrationMut: Migration {
    /// Adds an abstract table to the migration. The table state should
    /// represent the expected state after the migration has been
    /// applied. It is expected that all tables will be added to the
    /// migration in this fashion.
    fn write_table(&mut self, table: &ATable) -> Result<()>;

    /// Delete the table with the given name. Note that simply
    /// deleting a table in code does not work -- it will remain with
    /// its last known schema unless explicitly deleted. See also the
    /// butane cli command `butane delete table <TABLE>`.
    fn delete_table(&mut self, name: &str) -> Result<()>;

    /// Set the backend-specific commands to apply/undo this migration.
    fn add_sql(&mut self, backend_name: &str, up_sql: &str, down_sql: &str) -> Result<()>;

    /// Adds a TypeKey -> SqlType mapping. Only meaningful on the special current migration.
    fn add_type(&mut self, key: TypeKey, sqltype: DeferredSqlType) -> Result<()>;

    /// Set the name of the migration before this one.
    fn set_migration_from(&mut self, prev: Option<String>) -> Result<()>;
}