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