Skip to main content

refinery_core/traits/
async.rs

1use crate::error::WrapMigrationError;
2use crate::traits::{
3    insert_migration_query, verify_migrations, GET_APPLIED_MIGRATIONS_QUERY,
4    GET_LAST_APPLIED_MIGRATION_QUERY,
5};
6use crate::{Error, Migration, Report, Target};
7
8use std::future::Future;
9use std::string::ToString;
10
11pub trait AsyncTransaction: Send {
12    type Error: std::error::Error + Send + Sync + 'static;
13
14    fn execute<'a, T: Iterator<Item = &'a str> + Send + 'a>(
15        &'a mut self,
16        queries: T,
17    ) -> impl Future<Output = Result<usize, Self::Error>> + Send + 'a;
18}
19
20pub trait AsyncQuery<T>: AsyncTransaction {
21    fn query<'a>(
22        &'a mut self,
23        query: &'a str,
24    ) -> impl Future<Output = Result<T, Self::Error>> + Send + 'a;
25}
26
27async fn migrate<T: AsyncTransaction>(
28    transaction: &mut T,
29    migrations: Vec<Migration>,
30    target: Target,
31    migration_table_name: &str,
32) -> Result<Report, Error> {
33    let mut applied_migrations = vec![];
34
35    for mut migration in migrations.into_iter() {
36        if let Target::Version(input_target) = target {
37            if input_target < migration.version() {
38                log::info!(
39                    "stopping at migration: {}, due to user option",
40                    input_target
41                );
42                break;
43            }
44        }
45
46        log::info!("applying migration: {}", migration);
47        migration.set_applied();
48        let update_query = insert_migration_query(&migration, migration_table_name);
49        transaction
50            .execute(
51                [
52                    migration.sql().as_ref().expect("sql must be Some!"),
53                    update_query.as_str(),
54                ]
55                .into_iter(),
56            )
57            .await
58            .migration_err(
59                &format!("error applying migration {migration}"),
60                Some(&applied_migrations),
61            )?;
62        applied_migrations.push(migration);
63    }
64    Ok(Report::new(applied_migrations))
65}
66
67async fn migrate_grouped<T: AsyncTransaction>(
68    transaction: &mut T,
69    migrations: Vec<Migration>,
70    target: Target,
71    migration_table_name: &str,
72) -> Result<Report, Error> {
73    let mut grouped_migrations = Vec::new();
74    let mut applied_migrations = Vec::new();
75
76    for mut migration in migrations.into_iter() {
77        if let Target::Version(input_target) | Target::FakeVersion(input_target) = target {
78            if input_target < migration.version() {
79                break;
80            }
81        }
82
83        migration.set_applied();
84        let query = insert_migration_query(&migration, migration_table_name);
85
86        let sql = migration.sql().expect("sql must be Some!").to_string();
87
88        // If Target is Fake, we only update schema migrations table
89        if !matches!(target, Target::Fake | Target::FakeVersion(_)) {
90            applied_migrations.push(migration);
91            grouped_migrations.push(sql);
92        }
93        grouped_migrations.push(query);
94    }
95
96    match target {
97        Target::Fake | Target::FakeVersion(_) => {
98            log::info!("not going to apply any migration as fake flag is enabled");
99        }
100        Target::Latest | Target::Version(_) => {
101            let migrations_display = applied_migrations
102                .iter()
103                .map(ToString::to_string)
104                .collect::<Vec<String>>()
105                .join("\n");
106            log::info!(
107                "going to apply batch migrations in single transaction:\n{migrations_display}"
108            );
109        }
110    };
111
112    if let Target::Version(input_target) = target {
113        log::info!(
114            "stopping at migration: {}, due to user option",
115            input_target
116        );
117    }
118
119    transaction
120        .execute(grouped_migrations.iter().map(AsRef::as_ref))
121        .await
122        .migration_err("error applying migrations", None)?;
123
124    Ok(Report::new(applied_migrations))
125}
126
127pub trait AsyncMigrate: AsyncQuery<Vec<Migration>>
128where
129    Self: Sized,
130{
131    // Needed cause some database vendors like Mssql have a non sql standard way of checking the migrations table
132    fn assert_migrations_table_query(migration_table_name: &str) -> String {
133        super::assert_migrations_table_query(migration_table_name)
134    }
135
136    fn get_last_applied_migration_query(migration_table_name: &str) -> String {
137        GET_LAST_APPLIED_MIGRATION_QUERY.replace("%MIGRATION_TABLE_NAME%", migration_table_name)
138    }
139
140    fn get_applied_migrations_query(migration_table_name: &str) -> String {
141        GET_APPLIED_MIGRATIONS_QUERY.replace("%MIGRATION_TABLE_NAME%", migration_table_name)
142    }
143
144    fn get_last_applied_migration<'a>(
145        &'a mut self,
146        migration_table_name: &'a str,
147    ) -> impl Future<Output = Result<Option<Migration>, Error>> + Send + 'a {
148        async move {
149            let mut migrations = self
150                .query(Self::get_last_applied_migration_query(migration_table_name).as_ref())
151                .await
152                .migration_err("error getting last applied migration", None)?;
153
154            Ok(migrations.pop())
155        }
156    }
157
158    fn get_applied_migrations<'a>(
159        &'a mut self,
160        migration_table_name: &'a str,
161    ) -> impl Future<Output = Result<Vec<Migration>, Error>> + Send + 'a {
162        async move {
163            let migrations = self
164                .query(Self::get_applied_migrations_query(migration_table_name).as_ref())
165                .await
166                .migration_err("error getting applied migrations", None)?;
167
168            Ok(migrations)
169        }
170    }
171
172    fn migrate<'a>(
173        &'a mut self,
174        migrations: &'a [Migration],
175        abort_divergent: bool,
176        abort_missing: bool,
177        grouped: bool,
178        target: Target,
179        migration_table_name: &'a str,
180    ) -> impl Future<Output = Result<Report, Error>> + Send + 'a {
181        async move {
182            self.execute(
183                [Self::assert_migrations_table_query(migration_table_name).as_ref()].into_iter(),
184            )
185            .await
186            .migration_err("error asserting migrations table", None)?;
187
188            let applied_migrations = self
189                .get_applied_migrations(migration_table_name)
190                .await
191                .migration_err("error getting current schema version", None)?;
192
193            let migrations = verify_migrations(
194                applied_migrations,
195                migrations.to_vec(),
196                abort_divergent,
197                abort_missing,
198            )?;
199
200            if migrations.is_empty() {
201                log::info!("no migrations to apply");
202            }
203
204            if grouped || matches!(target, Target::Fake | Target::FakeVersion(_)) {
205                migrate_grouped(self, migrations, target, migration_table_name).await
206            } else {
207                migrate(self, migrations, target, migration_table_name).await
208            }
209        }
210    }
211}