1use rorm_db::executor::{Executor, Nothing};
4use rorm_db::sql::alter_table::{AlterTable, AlterTableOperation};
5use rorm_db::sql::create_index::CreateIndex;
6use rorm_db::sql::create_table::CreateTable;
7use rorm_db::sql::drop_index::DropIndex;
8use rorm_db::sql::drop_table::DropTable;
9use rorm_db::sql::insert::Insert;
10use rorm_db::sql::value::Value;
11use rorm_db::sql::DBImpl;
12use rorm_db::transaction::{Transaction, TransactionError};
13use rorm_declaration::migration::{Migration, Operation};
14use thiserror::Error;
15
16pub async fn apply_migration(
24 db: impl Executor<'_>,
25 migration: &Migration,
26 last_migration_table_name: &str,
27) -> Result<(), ApplyMigrationError> {
28 let mut tx = db
29 .ensure_transaction()
30 .await
31 .map_err(|error| ApplyMigrationError {
32 error,
33 location: ApplyMigrationErrorLocation::StartTransaction,
34 })?;
35
36 for (index, operation) in migration.operations.iter().enumerate() {
37 apply_operation(tx.get_transaction(), operation)
38 .await
39 .map_err(|error| ApplyMigrationError {
40 error,
41 location: ApplyMigrationErrorLocation::ApplyOperation(index),
42 })?;
43 }
44
45 let (query_string, bind_params) = tx
46 .get_transaction()
47 .dialect()
48 .insert(
49 last_migration_table_name,
50 &["migration_id"],
51 &[&[Value::I32(migration.id as i32)]],
52 None,
53 )
54 .rollback_transaction()
55 .build();
56
57 tx.get_transaction()
58 .execute::<Nothing>(query_string, bind_params)
59 .await
60 .map_err(|error| ApplyMigrationError {
61 error,
62 location: ApplyMigrationErrorLocation::UpdateLastMigration,
63 })?;
64
65 tx.commit().await.map_err(|x| ApplyMigrationError {
66 error: match x {
67 TransactionError::Database(x) => x,
68 TransactionError::Hook(_) => unreachable!("rorm-cli does not use hooks"),
69 },
70 location: ApplyMigrationErrorLocation::CommitTransaction,
71 })?;
72
73 Ok(())
74}
75
76#[derive(Debug, Error)]
82#[error("{location}: {error}")]
83pub struct ApplyMigrationError {
84 #[source]
86 pub error: rorm_db::Error,
87
88 pub location: ApplyMigrationErrorLocation,
90}
91
92#[derive(Debug, Error)]
94pub enum ApplyMigrationErrorLocation {
95 #[error("Failed to start transaction")]
97 StartTransaction,
98
99 #[error("Failed to apply operation {}", .0)]
101 ApplyOperation(usize),
102
103 #[error("Failed to update last migration")]
105 UpdateLastMigration,
106
107 #[error("Failed to commit transaction")]
109 CommitTransaction,
110}
111
112pub async fn apply_operation(
114 tx: &mut Transaction,
115 operation: &Operation,
116) -> Result<(), rorm_db::Error> {
117 let db_impl = tx.dialect();
118
119 match operation {
120 Operation::CreateModel { name, fields } => {
121 let mut create_table = db_impl.create_table(name.as_str());
122
123 for field in fields {
124 create_table = create_table.add_column(db_impl.create_column(
125 name.as_str(),
126 field.name.as_str(),
127 field.db_type,
128 &field.annotations,
129 ));
130 }
131
132 let statements = create_table.build()?;
133
134 for (query_string, query_bind_params) in statements {
135 tx.execute::<Nothing>(query_string, query_bind_params)
136 .await?;
137 }
138 }
139 Operation::RenameModel { old, new } => {
140 let statements = db_impl
141 .alter_table(
142 old.as_str(),
143 AlterTableOperation::RenameTo {
144 name: new.to_string(),
145 },
146 )
147 .build()?;
148
149 for (query_string, query_bind_params) in statements {
150 tx.execute::<Nothing>(query_string, query_bind_params)
151 .await?;
152 }
153 }
154 Operation::DeleteModel { name } => {
155 let query_string = db_impl.drop_table(name.as_str()).build();
156
157 tx.execute::<Nothing>(query_string, Vec::new()).await?;
158 }
159 Operation::CreateField { model, field } => {
160 let statements = db_impl
161 .alter_table(
162 model.as_str(),
163 AlterTableOperation::AddColumn {
164 operation: db_impl.create_column(
165 model.as_str(),
166 field.name.as_str(),
167 field.db_type,
168 &field.annotations,
169 ),
170 },
171 )
172 .build()?;
173
174 for (query_string, query_bind_params) in statements {
175 tx.execute::<Nothing>(query_string, query_bind_params)
176 .await?;
177 }
178 }
179 Operation::RenameField {
180 table_name,
181 old,
182 new,
183 } => {
184 let statements = db_impl
185 .alter_table(
186 table_name.as_str(),
187 AlterTableOperation::RenameColumnTo {
188 column_name: old.to_string(),
189 new_column_name: new.to_string(),
190 },
191 )
192 .build()?;
193
194 for (query_string, query_bind_params) in statements {
195 tx.execute::<Nothing>(query_string, query_bind_params)
196 .await?;
197 }
198 }
199 Operation::DeleteField { model, name } => {
200 let statements = db_impl
201 .alter_table(
202 model.as_str(),
203 AlterTableOperation::DropColumn { name: name.clone() },
204 )
205 .build()?;
206
207 for (query_string, query_bind_params) in statements {
208 tx.execute::<Nothing>(query_string, query_bind_params)
209 .await?;
210 }
211 }
212 Operation::CreateIndex { model, index } => {
213 let name = index.sql_name(model);
214
215 let mut create_index = db_impl.create_index(name.as_str(), model.as_str());
216 for column in &index.columns {
217 create_index = create_index.add_column(column.as_str());
218 }
219
220 tx.execute::<Nothing>(create_index.build()?, Vec::new())
221 .await?;
222 }
223 Operation::DeleteIndex { model, index } => {
224 let query_string = db_impl.drop_index(index.sql_name(model).as_str()).build();
225
226 tx.execute::<Nothing>(query_string, Vec::new()).await?;
227 }
228 #[allow(unused_variables)]
229 Operation::RawSQL {
230 mysql,
231 postgres,
232 sqlite,
233 ..
234 } => match db_impl {
235 #[cfg(feature = "sqlite")]
236 DBImpl::SQLite => tx.execute::<Nothing>(sqlite.clone(), Vec::new()).await?,
237 #[cfg(feature = "postgres")]
238 DBImpl::Postgres => tx.execute::<Nothing>(postgres.clone(), Vec::new()).await?,
239 },
240 }
241
242 Ok(())
243}