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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use std::collections::{HashMap};


use anyhow::Result;
use crate::query::AlterTable;

use crate::query::AlterAction;
use crate::query::CreateIndex;
use crate::query::CreateTable;
use crate::schema::{Schema};
use crate::{Dialect, ToSql};

#[derive(Debug, Clone, Default)]
pub struct MigrationOptions {
    pub debug: bool,
}


pub fn migrate(current: Schema, desired: Schema, _options: &MigrationOptions) -> Result<Migration> {
    let current_tables = current.tables.iter().map(|t| (&t.name, t)).collect::<HashMap<_, _>>();
    let desired_tables = desired.tables.iter().map(|t| (&t.name, t)).collect::<HashMap<_, _>>();

    let mut debug_results = vec![];
    let mut statements = Vec::new();
    // new tables
    for (_name, table) in desired_tables.iter().filter(|(name, _)| !current_tables.contains_key(*name)) {
        let statement = Statement::CreateTable(CreateTable::from_table(table));
        statements.push(statement);
    }

    // alter existing tables
    for (name, desired_table) in desired_tables.iter().filter(|(name, _)| current_tables.contains_key(*name)) {
        let current_table = current_tables[name];
        let current_columns = current_table.columns.iter().map(|c| (&c.name, c)).collect::<HashMap<_, _>>();
        // add columns
        let mut actions = vec![];
        for desired_column in desired_table.columns.iter() {
            if let Some(current) = current_columns.get(&desired_column.name) {
                if current.nullable != desired_column.nullable {
                    actions.push(AlterAction::set_nullable(desired_column.name.clone(), desired_column.nullable));
                }
                if current.typ != desired_column.typ {
                    actions.push(AlterAction::set_type(desired_column.name.clone(), desired_column.typ.clone()));
                };
            } else {
                actions.push(AlterAction::AddColumn {
                    column: desired_column.clone(),
                });
            }
        }
        if actions.is_empty() {
            debug_results.push(DebugResults::TablesIdentical(name.to_string()));
        } else {
            statements.push(Statement::AlterTable(AlterTable {
                schema: desired_table.schema.clone(),
                name: desired_table.name.clone(),
                actions,
            }));
        }
    }

    Ok(Migration {
        statements,
        debug_results,
    })
}

#[derive(Debug)]
pub struct Migration {
    pub statements: Vec<Statement>,
    pub debug_results: Vec<DebugResults>,
}

impl Migration {
    pub fn is_empty(&self) -> bool {
        self.statements.is_empty()
    }

    pub fn set_schema(&mut self, schema_name: &str) {
        for statement in &mut self.statements {
            statement.set_schema(schema_name);
        }
    }
}

#[derive(Debug)]
pub enum Statement {
    CreateTable(CreateTable),
    CreateIndex(CreateIndex),
    AlterTable(AlterTable),
}

impl Statement {
    pub fn set_schema(&mut self, schema_name: &str) {
        match self {
            Statement::CreateTable(ref mut create_table) => {
                create_table.schema = Some(schema_name.to_string());
            }
            Statement::AlterTable(ref mut alter_table) => {
                alter_table.schema = Some(schema_name.to_string());
            }
            Statement::CreateIndex(ref mut create_index) => {
                create_index.schema = Some(schema_name.to_string());
            }
        }
    }
}

impl ToSql for Statement {
    fn write_sql(&self, buf: &mut String, dialect: Dialect) {
        use Statement::*;
        match self {
            CreateTable(c) => c.write_sql(buf, dialect),
            CreateIndex(c) => c.write_sql(buf, dialect),
            AlterTable(a) => a.write_sql(buf, dialect),
        }
    }
}

#[derive(Debug)]
pub enum DebugResults {
    TablesIdentical(String)
}

impl DebugResults {
    pub fn table_name(&self) -> &str {
        match self {
            DebugResults::TablesIdentical(name) => name,
        }
    }
}