docbox_database/migrations/
mod.rs1use crate::{
2 DbExecutor, DbResult, DbTransaction,
3 models::{
4 root_migration::{CreateRootMigration, RootMigration},
5 tenant::Tenant,
6 tenant_migration::{CreateTenantMigration, TenantMigration},
7 },
8};
9use chrono::Utc;
10use std::ops::DerefMut;
11
12pub const ROOT_MIGRATIONS: &[(&str, &str)] = &[
13 (
14 "m1_create_tenants_table",
15 include_str!("./root/m1_create_tenants_table.sql"),
16 ),
17 (
18 "m2_create_tenant_migrations_table",
19 include_str!("./root/m2_create_tenant_migrations_table.sql"),
20 ),
21 (
22 "m3_create_storage_bucket_index",
23 include_str!("./root/m3_create_storage_bucket_index.sql"),
24 ),
25 (
26 "m4_tenant_migrations_update_constraint",
27 include_str!("./root/m4_tenant_migrations_update_constraint.sql"),
28 ),
29];
30
31pub const TENANT_MIGRATIONS: &[(&str, &str)] = &[
32 (
33 "m1_create_users_table",
34 include_str!("./tenant/m1_create_users_table.sql"),
35 ),
36 (
37 "m2_create_document_box_table",
38 include_str!("./tenant/m2_create_document_box_table.sql"),
39 ),
40 (
41 "m3_create_folders_table",
42 include_str!("./tenant/m3_create_folders_table.sql"),
43 ),
44 (
45 "m4_create_files_table",
46 include_str!("./tenant/m4_create_files_table.sql"),
47 ),
48 (
49 "m5_create_generated_files_table",
50 include_str!("./tenant/m5_create_generated_files_table.sql"),
51 ),
52 (
53 "m6_create_links_table",
54 include_str!("./tenant/m6_create_links_table.sql"),
55 ),
56 (
57 "m7_create_edit_history_table",
58 include_str!("./tenant/m7_create_edit_history_table.sql"),
59 ),
60 (
61 "m8_create_tasks_table",
62 include_str!("./tenant/m8_create_tasks_table.sql"),
63 ),
64 (
65 "m9_create_presigned_upload_tasks_table",
66 include_str!("./tenant/m9_create_presigned_upload_tasks_table.sql"),
67 ),
68 (
69 "m10_add_pinned_column",
70 include_str!("./tenant/m10_add_pinned_column.sql"),
71 ),
72 (
73 "m11_create_links_resolved_metadata_table",
74 include_str!("./tenant/m11_create_links_resolved_metadata_table.sql"),
75 ),
76 (
77 "m12_create_composite_types_and_views",
78 include_str!("./tenant/m12_create_composite_types_and_views.sql"),
79 ),
80 (
81 "m13_create_folder_functions",
82 include_str!("./tenant/m13_create_folder_functions.sql"),
83 ),
84 (
85 "m14_create_link_functions",
86 include_str!("./tenant/m14_create_link_functions.sql"),
87 ),
88 (
89 "m15_create_file_functions",
90 include_str!("./tenant/m15_create_file_functions.sql"),
91 ),
92 (
93 "m16_docbox_tasks_constraint",
94 include_str!("./tenant/m16_docbox_tasks_constraint.sql"),
95 ),
96];
97
98pub async fn initialize_root_migrations(db: impl DbExecutor<'_>) -> DbResult<()> {
102 sqlx::raw_sql(include_str!("./root/m0_create_root_migrations_table.sql"))
103 .execute(db)
104 .await?;
105
106 Ok(())
107}
108
109pub async fn get_pending_tenant_migrations(
111 db: impl DbExecutor<'_>,
112 tenant: &Tenant,
113) -> DbResult<Vec<String>> {
114 let migrations = TenantMigration::find_by_tenant(db, tenant.id, &tenant.env).await?;
115
116 let pending = TENANT_MIGRATIONS
117 .iter()
118 .filter(|(migration_name, _migration)| {
119 !migrations
121 .iter()
122 .any(|migration| migration.name.eq(migration_name))
123 })
124 .map(|(migration_name, _migration)| migration_name.to_string())
125 .collect();
126
127 Ok(pending)
128}
129
130pub async fn get_pending_root_migrations(db: impl DbExecutor<'_>) -> DbResult<Vec<String>> {
132 let migrations = RootMigration::all(db).await?;
133
134 let pending = ROOT_MIGRATIONS
135 .iter()
136 .filter(|(migration_name, _migration)| {
137 !migrations
139 .iter()
140 .any(|migration| migration.name.eq(migration_name))
141 })
142 .map(|(migration_name, _migration)| migration_name.to_string())
143 .collect();
144
145 Ok(pending)
146}
147
148pub async fn apply_tenant_migrations(
153 root_t: &mut DbTransaction<'_>,
154 t: &mut DbTransaction<'_>,
155 tenant: &Tenant,
156 target_migration_name: Option<&str>,
157) -> DbResult<()> {
158 let migrations =
159 TenantMigration::find_by_tenant(root_t.deref_mut(), tenant.id, &tenant.env).await?;
160
161 for (migration_name, migration) in TENANT_MIGRATIONS {
162 if target_migration_name
164 .is_some_and(|target_migration_name| target_migration_name.ne(*migration_name))
165 {
166 continue;
167 }
168
169 if migrations
171 .iter()
172 .any(|migration| migration.name.eq(migration_name))
173 {
174 continue;
175 }
176
177 apply_migration(t, migration_name, migration).await?;
179
180 TenantMigration::create(
182 root_t.deref_mut(),
183 CreateTenantMigration {
184 tenant_id: tenant.id,
185 env: tenant.env.clone(),
186 name: migration_name.to_string(),
187 applied_at: Utc::now(),
188 },
189 )
190 .await?;
191 }
192
193 Ok(())
194}
195
196pub async fn apply_root_migrations(
201 root_t: &mut DbTransaction<'_>,
202 target_migration_name: Option<&str>,
203) -> DbResult<()> {
204 let migrations = RootMigration::all(root_t.deref_mut()).await?;
205
206 for (migration_name, migration) in ROOT_MIGRATIONS {
207 if target_migration_name
209 .is_some_and(|target_migration_name| target_migration_name.ne(*migration_name))
210 {
211 continue;
212 }
213
214 if migrations
216 .iter()
217 .any(|migration| migration.name.eq(migration_name))
218 {
219 continue;
220 }
221
222 apply_migration(root_t, migration_name, migration).await?;
224
225 RootMigration::create(
227 root_t.deref_mut(),
228 CreateRootMigration {
229 name: migration_name.to_string(),
230 applied_at: Utc::now(),
231 },
232 )
233 .await?;
234 }
235
236 Ok(())
237}
238
239pub async fn force_apply_tenant_migrations(
243 t: &mut DbTransaction<'_>,
244 target_migration_name: Option<&str>,
245) -> DbResult<()> {
246 for (migration_name, migration) in TENANT_MIGRATIONS {
247 if target_migration_name
249 .is_some_and(|target_migration_name| target_migration_name.ne(*migration_name))
250 {
251 continue;
252 }
253
254 apply_migration(t, migration_name, migration).await?;
255 }
256
257 Ok(())
258}
259
260pub async fn apply_migration(
262 db: &mut DbTransaction<'_>,
263 migration_name: &str,
264 migration: &str,
265) -> DbResult<()> {
266 let queries = migration
268 .split(';')
269 .map(|query| query.trim())
270 .filter(|query| !query.is_empty());
271
272 for query in queries {
273 let result = sqlx::query(query)
274 .execute(db.deref_mut())
275 .await
276 .inspect_err(|error| {
277 tracing::error!(?error, ?migration_name, "failed to perform migration")
278 })?;
279 let rows_affected = result.rows_affected();
280
281 tracing::debug!(?migration_name, ?rows_affected, "applied migration query");
282 }
283
284 Ok(())
285}