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