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
use std::path::Path;

#[cfg(feature = "postgres")]
use postgres::Client;
#[cfg(feature = "tokio-postgres")]
use tokio_postgres::Client;

#[cfg(any(feature = "postgres", feature = "tokio-postgres"))]
use crate::error::BoxError;

mod error;
mod fs;
mod migration;

const DEFAULT_MIGRATIONS_TABLE: &str = "__migrations";

pub struct PostgresMigrator<P: AsRef<Path>> {
    migrations_path: P,
    migrations_table: String,
    ignore_missing_migrations: bool,
}

impl<P: AsRef<Path>> PostgresMigrator<P> {
    pub fn new(migrations_path: P) -> Self {
        Self {
            migrations_path,
            migrations_table: DEFAULT_MIGRATIONS_TABLE.to_string(),
            ignore_missing_migrations: false,
        }
    }

    pub fn migrations_table<T: Into<String>>(mut self, migrations_table: T) -> Self {
        self.migrations_table = migrations_table.into();
        self
    }

    pub fn ignore_missing_migrations(mut self, ignore_missing: bool) -> Self {
        self.ignore_missing_migrations = ignore_missing;
        self
    }

    #[cfg(feature = "postgres")]
    pub fn migrate(&self, pg: &mut Client) -> Result<(), BoxError> {
        let migrations = fs::load_migrations(self.migrations_path.as_ref())?;

        migration::ensure_migrations_table_exists(pg, &self.migrations_table)?;

        let applied = migration::validate_applied(pg, &self.migrations_table, &migrations)?;

        for migration in migrations {
            if applied.contains(&migration.version) {
                continue;
            }

            migration::apply(pg, &self.migrations_table, &migration)?;
        }

        Ok(())
    }

    #[cfg(feature = "tokio-postgres")]
    pub async fn migrate(&self, pg: &mut Client) -> Result<(), BoxError> {
        let migrations = fs::load_migrations(self.migrations_path.as_ref())?;

        migration::ensure_migrations_table_exists(pg, &self.migrations_table).await?;

        let applied = migration::validate_applied(pg, &self.migrations_table, &migrations).await?;

        for migration in migrations {
            if applied.contains(&migration.version) {
                continue;
            }

            migration::apply(pg, &self.migrations_table, &migration).await?;
        }

        Ok(())
    }
}