refinery_core/traits/
async.rs1use 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 !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 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}