barrel/
migration.rs

1//! Core migration creation handler
2//!
3//! A migration can be done for a specific schema which contains
4//! multiple additions or removables from a database or table.
5//!
6//! At the end of crafting a migration you can use `Migration::exec` to
7//! get the raw SQL string for a database backend or `Migration::revert`
8//! to try to auto-infer the migration rollback. In cases where that
9//! can't be done the `Result<String, RevertError>` will not unwrap.
10//!
11//! You can also use `Migration::exec` with your SQL connection for convenience
12//! if you're a library developer.
13
14use crate::table::{Table, TableMeta};
15use crate::DatabaseChange;
16
17use crate::backend::{SqlGenerator, SqlVariant};
18use crate::connectors::SqlRunner;
19
20use std::rc::Rc;
21
22/// Represents a schema migration on a database
23pub struct Migration {
24    #[doc(hidden)]
25    pub schema: Option<String>,
26    #[doc(hidden)]
27    pub changes: Vec<DatabaseChange>,
28}
29
30impl Migration {
31    pub fn new() -> Migration {
32        Migration {
33            schema: None,
34            changes: Vec::new(),
35        }
36    }
37
38    /// Specify a database schema name for this migration
39    pub fn schema<S: Into<String>>(self, schema: S) -> Migration {
40        Self {
41            schema: Some(schema.into()),
42            ..self
43        }
44    }
45
46    /// Creates the SQL for this migration for a specific backend
47    ///
48    /// This function copies state and does not touch the original
49    /// migration layout. This allows you to call `revert` later on
50    /// in the process to auto-infer the down-behaviour
51    pub fn make<T: SqlGenerator>(&self) -> String {
52        use DatabaseChange::*;
53
54        /* What happens in make, stays in make (sort of) */
55        let mut changes = self.changes.clone();
56        let schema = self.schema.as_ref().map(|s| s.as_str());
57
58        changes.iter_mut().fold(String::new(), |mut sql, change| {
59            match change {
60                &mut CreateTable(ref mut t, ref mut cb)
61                | &mut CreateTableIfNotExists(ref mut t, ref mut cb) => {
62                    cb(t); // Run the user code
63                    let sql_changes = t.make::<T>(false, schema);
64
65                    let name = t.meta.name().clone();
66                    sql.push_str(&match change {
67                        CreateTable(_, _) => T::create_table(&name, schema),
68                        CreateTableIfNotExists(_, _) => {
69                            T::create_table_if_not_exists(&name, schema)
70                        }
71                        _ => unreachable!(),
72                    });
73                    sql.push_str(" (");
74                    let l = sql_changes.columns.len();
75                    for (i, slice) in sql_changes.columns.iter().enumerate() {
76                        sql.push_str(slice);
77
78                        if i < l - 1 {
79                            sql.push_str(", ");
80                        }
81                    }
82
83                    let l = sql_changes.constraints.len();
84                    for (i, slice) in sql_changes.constraints.iter().enumerate() {
85                        if sql_changes.columns.len() > 0 && i == 0 {
86                            sql.push_str(", ")
87                        }
88
89                        sql.push_str(slice);
90
91                        if i < l - 1 {
92                            sql.push_str(", ")
93                        }
94                    }
95
96                    if let Some(ref primary_key) = sql_changes.primary_key {
97                        sql.push_str(", ");
98                        sql.push_str(primary_key);
99                    };
100
101                    let l = sql_changes.foreign_keys.len();
102                    for (i, slice) in sql_changes.foreign_keys.iter().enumerate() {
103                        if sql_changes.columns.len() > 0 && i == 0 {
104                            sql.push_str(", ")
105                        }
106
107                        sql.push_str(slice);
108
109                        if i < l - 1 {
110                            sql.push_str(", ")
111                        }
112                    }
113
114                    sql.push_str(")");
115
116                    // Add additional index columns
117                    if sql_changes.indices.len() > 0 {
118                        sql.push_str(";");
119                        sql.push_str(&sql_changes.indices.join(";"));
120                    }
121                }
122                &mut DropTable(ref name) => sql.push_str(&T::drop_table(name, schema)),
123                &mut DropTableIfExists(ref name) => {
124                    sql.push_str(&T::drop_table_if_exists(name, schema))
125                }
126                &mut RenameTable(ref old, ref new) => {
127                    sql.push_str(&T::rename_table(old, new, schema))
128                }
129                &mut ChangeTable(ref mut t, ref mut cb) => {
130                    cb(t);
131                    let sql_changes = t.make::<T>(true, schema);
132
133                    sql.push_str(&T::alter_table(&t.meta.name(), schema));
134                    sql.push_str(" ");
135
136                    let l = sql_changes.columns.len();
137                    for (i, slice) in sql_changes.columns.iter().enumerate() {
138                        sql.push_str(slice);
139
140                        if i < l - 1 {
141                            sql.push_str(", ");
142                        }
143                    }
144
145                    let l = sql_changes.foreign_keys.len();
146                    for (i, slice) in sql_changes.foreign_keys.iter().enumerate() {
147                        if sql_changes.columns.len() > 0 && i == 0 {
148                            sql.push_str(", ")
149                        }
150
151                        sql.push_str("ADD ");
152                        sql.push_str(slice);
153
154                        if i < l - 1 {
155                            sql.push_str(", ")
156                        }
157                    }
158
159                    if let Some(ref primary_key) = sql_changes.primary_key {
160                        sql.push_str(", ");
161                        sql.push_str("ADD ");
162                        sql.push_str(primary_key);
163                    };
164
165                    // Add additional index columns
166                    if sql_changes.indices.len() > 0 {
167                        sql.push_str(";");
168                        sql.push_str(&sql_changes.indices.join(";"));
169                    }
170                }
171
172                &mut CustomLine(ref line) => sql.push_str(line.as_str()),
173            }
174
175            sql.push_str(";");
176            sql
177        })
178    }
179
180    /// The same as `make` but making a run-time check for sql variant
181    ///
182    /// The `SqlVariant` type is populated based on the backends
183    /// that are being selected at compile-time.
184    ///
185    /// This function panics if the provided variant is empty!
186    pub fn make_from(&self, variant: SqlVariant) -> String {
187        variant.run_for(self)
188    }
189
190    /// Inject a line of custom SQL into the top-level migration scope
191    ///
192    /// This is a bypass to the barrel typesystem, in case there is
193    /// something your database supports that barrel doesn't, or if
194    /// there is an issue with the way that barrel represents types.
195    /// It does however mean that the SQL provided needs to be
196    /// specific for one database, meaning that future migrations
197    /// might become cumbersome.
198    pub fn inject_custom<S: Into<String>>(&mut self, sql: S) {
199        self.changes.push(DatabaseChange::CustomLine(sql.into()));
200    }
201
202    /// Automatically infer the `down` step of this migration
203    ///
204    /// Will thrown an error if behaviour is ambiguous or not
205    /// possible to infer (e.g. revert a `drop_table`)
206    pub fn revert<T: SqlGenerator>(&self) -> String {
207        unimplemented!()
208    }
209
210    /// Pass a reference to a migration toolkit runner which will
211    /// automatically generate and execute
212    pub fn execute<S: SqlGenerator, T: SqlRunner>(&self, runner: &mut T) {
213        runner.execute(self.make::<S>());
214    }
215
216    /// Create a new table with a specific name
217    pub fn create_table<S: Into<String>, F: 'static>(&mut self, name: S, cb: F) -> &mut TableMeta
218    where
219        F: Fn(&mut Table),
220    {
221        self.changes
222            .push(DatabaseChange::CreateTable(Table::new(name), Rc::new(cb)));
223
224        match self.changes.last_mut().unwrap() {
225            &mut DatabaseChange::CreateTable(ref mut t, _) => &mut t.meta,
226            _ => unreachable!(),
227        }
228    }
229
230    /// Create a new table *only* if it doesn't exist yet
231    pub fn create_table_if_not_exists<S: Into<String>, F: 'static>(
232        &mut self,
233        name: S,
234        cb: F,
235    ) -> &mut TableMeta
236    where
237        F: Fn(&mut Table),
238    {
239        self.changes.push(DatabaseChange::CreateTableIfNotExists(
240            Table::new(name),
241            Rc::new(cb),
242        ));
243
244        match self.changes.last_mut().unwrap() {
245            &mut DatabaseChange::CreateTableIfNotExists(ref mut t, _) => &mut t.meta,
246            _ => unreachable!(),
247        }
248    }
249
250    /// Change fields on an existing table
251    pub fn change_table<S: Into<String>, F: 'static>(&mut self, name: S, cb: F)
252    where
253        F: Fn(&mut Table),
254    {
255        let t = Table::new(name);
256        let c = DatabaseChange::ChangeTable(t, Rc::new(cb));
257        self.changes.push(c);
258    }
259
260    /// Rename a table
261    pub fn rename_table<S: Into<String>>(&mut self, old: S, new: S) {
262        self.changes
263            .push(DatabaseChange::RenameTable(old.into(), new.into()));
264    }
265
266    /// Drop an existing table
267    pub fn drop_table<S: Into<String>>(&mut self, name: S) {
268        self.changes.push(DatabaseChange::DropTable(name.into()));
269    }
270
271    /// Only drop a table if it exists
272    pub fn drop_table_if_exists<S: Into<String>>(&mut self, name: S) {
273        self.changes
274            .push(DatabaseChange::DropTableIfExists(name.into()));
275    }
276}