assert_migrator_reversible/
assert_migrator_reversible.rs

1use ::sea_orm_migration::prelude::MigratorTrait;
2
3#[cfg(feature = "tokio")]
4use ::tokio::runtime::Builder;
5#[cfg(feature = "tokio")]
6use ::tokio::runtime::Runtime;
7
8use crate::build_db_connection;
9use crate::DbConnection;
10
11use crate::queries::get_table_schemas;
12use crate::queries::get_type_schemas;
13use crate::queries::TableSchema;
14use crate::queries::TypeSchema;
15
16///
17/// Runs a given `Migrator` against a new database.
18/// It's migrations will be run up and down in series.
19///
20/// If a Migration differs when going down, an error will be raised.
21///
22/// Note for performance reasons, this works in reverse order of migrations.
23///
24#[cfg(feature = "tokio")]
25pub fn assert_migrator_reversible<'a, M>(migrator: M, db_conn: Option<DbConnection<'a>>)
26where
27    M: MigratorTrait,
28{
29    build_tokio_runtime()
30        .block_on(async move { assert_migrator_reversible_async(migrator, db_conn).await });
31}
32
33///
34/// This is an `async` version of `assert_migrator_reversible`.
35///
36pub async fn assert_migrator_reversible_async<'a, M>(migrator: M, db_conn: Option<DbConnection<'a>>)
37where
38    M: MigratorTrait,
39{
40    let maybe_index = find_index_of_non_reversible_migration_async(migrator, db_conn).await;
41    if let Some(index) = maybe_index {
42        panic!("Migration at index {} is not reversible", index);
43    }
44}
45
46///
47/// Returns the index of the first migration it can find, which is not
48/// reversible.
49///
50/// `None` is returned if they are all reversible.
51///
52/// Note for performance reasons, this will check migrations in reverse order.
53///
54/*
55 * The plan is to use this in build tests.
56 * So most of the time we should expect the test to pass.
57 * We optimise for this event.
58 *
59 * The fast algorithm I know of ...
60 *  - Run each migration in order, and store the structure as we go up.
61 *  - Then run each migration down. Find the first that doesn't match.
62 *  - This results in searching in reverse order.
63 *
64 */
65#[cfg(feature = "tokio")]
66pub fn find_index_of_non_reversible_migration<'a, M>(
67    migrator: M,
68    db_conn: Option<DbConnection<'a>>,
69) -> Option<usize>
70where
71    M: MigratorTrait,
72{
73    build_tokio_runtime().block_on(async move {
74        find_index_of_non_reversible_migration_async(migrator, db_conn).await
75    })
76}
77
78///
79/// This is an `async` version of `find_index_of_non_reversible_migration`.
80///
81pub async fn find_index_of_non_reversible_migration_async<'a, M>(
82    _migrator: M,
83    db_conn: Option<DbConnection<'a>>,
84) -> Option<usize>
85where
86    M: MigratorTrait,
87{
88    let db_connection = build_db_connection(db_conn).await;
89    let num_migrations = M::migrations().len();
90    let mut migration_step_schemas: Vec<Vec<TableSchema>> = Vec::with_capacity(num_migrations);
91    let mut migration_type_schemas: Vec<Vec<TypeSchema>> = Vec::with_capacity(num_migrations);
92
93    // Go up all migrations.
94    for _ in 0..num_migrations {
95        let table_schemas = get_table_schemas(&db_connection).await;
96        migration_step_schemas.push(table_schemas);
97
98        let type_schemas = get_type_schemas(&db_connection).await;
99        migration_type_schemas.push(type_schemas);
100
101        <M as MigratorTrait>::up(&db_connection, Some(1))
102            .await
103            .expect("expect migration up should succeed");
104    }
105
106    // Go down all migrations.
107    for i in 0..num_migrations {
108        <M as MigratorTrait>::down(&db_connection, Some(1))
109            .await
110            .expect("expect migration down should succeed");
111
112        // Compare table schema changes
113        let down_table_schemas = get_table_schemas(&db_connection).await;
114        let up_table_schemas = migration_step_schemas
115            .pop()
116            .expect("expect up table schemas should exist");
117        if down_table_schemas != up_table_schemas {
118            for i in 0..up_table_schemas.len() {
119                let left = &up_table_schemas[i];
120                let right = &down_table_schemas[i];
121
122                if left != right {
123                    println!("{:#?}", left);
124                    println!("{:#?}", right);
125                    return Some(num_migrations - i - 1);
126                }
127            }
128            return Some(num_migrations - i - 1);
129        }
130
131        // Compare type schema changes
132        let down_type_schemas = get_type_schemas(&db_connection).await;
133        let up_type_schemas = migration_type_schemas
134            .pop()
135            .expect("expect up table schemas should exist");
136        if down_type_schemas != up_type_schemas {
137            return Some(num_migrations - i - 1);
138        }
139    }
140
141    None
142}
143
144#[cfg(feature = "tokio")]
145fn build_tokio_runtime() -> Runtime {
146    Builder::new_current_thread()
147        .enable_time()
148        .enable_io()
149        .build()
150        .expect("Expect to be able to start Tokio runtime for testing")
151}