Skip to main content

trailbase_refinery_core/traits/
async.rs

1use crate::error::WrapMigrationError;
2use crate::traits::{
3    insert_migration_query, verify_migrations, ASSERT_MIGRATIONS_TABLE_QUERY,
4    GET_APPLIED_MIGRATIONS_QUERY, GET_LAST_APPLIED_MIGRATION_QUERY,
5};
6use crate::{Error, Migration, Report, Target};
7
8use async_trait::async_trait;
9use std::ops::Deref;
10use std::string::ToString;
11
12#[async_trait]
13pub trait AsyncTransaction {
14    type Error: std::error::Error + Send + Sync + 'static;
15
16    async fn execute<'a, T: Iterator<Item = &'a str> + Send>(
17        &mut self,
18        queries: T,
19    ) -> Result<usize, Self::Error>;
20}
21
22#[async_trait]
23pub trait AsyncQuery<T>: AsyncTransaction {
24    async fn query(&mut self, query: &str) -> Result<T, Self::Error>;
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            log::info!(
102                "going to apply batch migrations in single transaction: {:#?}",
103                applied_migrations.iter().map(ToString::to_string)
104            );
105        }
106    };
107
108    if let Target::Version(input_target) = target {
109        log::info!(
110            "stopping at migration: {}, due to user option",
111            input_target
112        );
113    }
114
115    let refs = grouped_migrations.iter().map(AsRef::as_ref);
116
117    transaction
118        .execute(refs)
119        .await
120        .migration_err("error applying migrations", None)?;
121
122    Ok(Report::new(applied_migrations))
123}
124
125#[async_trait]
126pub trait AsyncMigrate: AsyncQuery<Vec<Migration>>
127where
128    Self: Sized,
129{
130    // Needed cause some database vendors like Mssql have a non sql standard way of checking the migrations table
131    fn assert_migrations_table_query(migration_table_name: &str) -> String {
132        ASSERT_MIGRATIONS_TABLE_QUERY.replace("%MIGRATION_TABLE_NAME%", migration_table_name)
133    }
134
135    async fn get_last_applied_migration(
136        &mut self,
137        migration_table_name: &str,
138    ) -> Result<Option<Migration>, Error> {
139        let mut migrations = self
140            .query(
141                &GET_LAST_APPLIED_MIGRATION_QUERY
142                    .replace("%MIGRATION_TABLE_NAME%", migration_table_name),
143            )
144            .await
145            .migration_err("error getting last applied migration", None)?;
146
147        Ok(migrations.pop())
148    }
149
150    async fn get_applied_migrations(
151        &mut self,
152        migration_table_name: &str,
153    ) -> Result<Vec<Migration>, Error> {
154        let migrations = self
155            .query(
156                &GET_APPLIED_MIGRATIONS_QUERY
157                    .replace("%MIGRATION_TABLE_NAME%", migration_table_name),
158            )
159            .await
160            .migration_err("error getting applied migrations", None)?;
161
162        Ok(migrations)
163    }
164
165    async fn migrate(
166        &mut self,
167        migrations: &[Migration],
168        abort_divergent: bool,
169        abort_missing: bool,
170        grouped: bool,
171        target: Target,
172        migration_table_name: &str,
173    ) -> Result<Report, Error> {
174        self.execute(
175            [Self::assert_migrations_table_query(migration_table_name).as_str()].into_iter(),
176        )
177        .await
178        .migration_err("error asserting migrations table", None)?;
179
180        let applied_migrations = self
181            .query(
182                &GET_APPLIED_MIGRATIONS_QUERY
183                    .replace("%MIGRATION_TABLE_NAME%", migration_table_name),
184            )
185            .await
186            .migration_err("error getting current schema version", None)?;
187
188        let migrations = verify_migrations(
189            applied_migrations,
190            migrations.to_vec(),
191            abort_divergent,
192            abort_missing,
193        )?;
194
195        if migrations.is_empty() {
196            log::info!("no migrations to apply");
197        }
198
199        if grouped || matches!(target, Target::Fake | Target::FakeVersion(_)) {
200            migrate_grouped(self, migrations, target, migration_table_name).await
201        } else {
202            migrate(self, migrations, target, migration_table_name).await
203        }
204    }
205}