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 "m5_tenant_iam_support",
31 include_str!("./root/m5_tenant_iam_support.sql"),
32 ),
33];
34
35pub const TENANT_MIGRATIONS: &[(&str, &str)] = &[
36 (
37 "m1_create_users_table",
38 include_str!("./tenant/m1_create_users_table.sql"),
39 ),
40 (
41 "m2_create_document_box_table",
42 include_str!("./tenant/m2_create_document_box_table.sql"),
43 ),
44 (
45 "m3_create_folders_table",
46 include_str!("./tenant/m3_create_folders_table.sql"),
47 ),
48 (
49 "m4_create_files_table",
50 include_str!("./tenant/m4_create_files_table.sql"),
51 ),
52 (
53 "m5_create_generated_files_table",
54 include_str!("./tenant/m5_create_generated_files_table.sql"),
55 ),
56 (
57 "m6_create_links_table",
58 include_str!("./tenant/m6_create_links_table.sql"),
59 ),
60 (
61 "m7_create_edit_history_table",
62 include_str!("./tenant/m7_create_edit_history_table.sql"),
63 ),
64 (
65 "m8_create_tasks_table",
66 include_str!("./tenant/m8_create_tasks_table.sql"),
67 ),
68 (
69 "m9_create_presigned_upload_tasks_table",
70 include_str!("./tenant/m9_create_presigned_upload_tasks_table.sql"),
71 ),
72 (
73 "m10_add_pinned_column",
74 include_str!("./tenant/m10_add_pinned_column.sql"),
75 ),
76 (
77 "m11_create_links_resolved_metadata_table",
78 include_str!("./tenant/m11_create_links_resolved_metadata_table.sql"),
79 ),
80 (
81 "m12_create_composite_types_and_views",
82 include_str!("./tenant/m12_create_composite_types_and_views.sql"),
83 ),
84 (
85 "m13_create_folder_functions",
86 include_str!("./tenant/m13_create_folder_functions.sql"),
87 ),
88 (
89 "m14_create_link_functions",
90 include_str!("./tenant/m14_create_link_functions.sql"),
91 ),
92 (
93 "m15_create_file_functions",
94 include_str!("./tenant/m15_create_file_functions.sql"),
95 ),
96 (
97 "m16_docbox_tasks_constraint",
98 include_str!("./tenant/m16_docbox_tasks_constraint.sql"),
99 ),
100];
101
102pub async fn initialize_root_migrations(db: impl DbExecutor<'_>) -> DbResult<()> {
106 sqlx::raw_sql(include_str!("./root/m0_create_root_migrations_table.sql"))
107 .execute(db)
108 .await?;
109
110 Ok(())
111}
112
113pub async fn get_pending_tenant_migrations(
115 db: impl DbExecutor<'_>,
116 tenant: &Tenant,
117) -> DbResult<Vec<String>> {
118 let migrations = TenantMigration::find_by_tenant(db, tenant.id, &tenant.env).await?;
119
120 let pending = TENANT_MIGRATIONS
121 .iter()
122 .filter(|(migration_name, _migration)| {
123 !migrations
125 .iter()
126 .any(|migration| migration.name.eq(migration_name))
127 })
128 .map(|(migration_name, _migration)| migration_name.to_string())
129 .collect();
130
131 Ok(pending)
132}
133
134pub async fn get_pending_root_migrations(db: impl DbExecutor<'_>) -> DbResult<Vec<String>> {
136 let migrations = RootMigration::all(db).await?;
137
138 let pending = ROOT_MIGRATIONS
139 .iter()
140 .filter(|(migration_name, _migration)| {
141 !migrations
143 .iter()
144 .any(|migration| migration.name.eq(migration_name))
145 })
146 .map(|(migration_name, _migration)| migration_name.to_string())
147 .collect();
148
149 Ok(pending)
150}
151
152pub async fn apply_tenant_migrations(
157 root_t: &mut DbTransaction<'_>,
158 t: &mut DbTransaction<'_>,
159 tenant: &Tenant,
160 target_migration_name: Option<&str>,
161) -> DbResult<()> {
162 let migrations =
163 TenantMigration::find_by_tenant(root_t.deref_mut(), tenant.id, &tenant.env).await?;
164
165 for (migration_name, migration) in TENANT_MIGRATIONS {
166 if target_migration_name
168 .is_some_and(|target_migration_name| target_migration_name.ne(*migration_name))
169 {
170 continue;
171 }
172
173 if migrations
175 .iter()
176 .any(|migration| migration.name.eq(migration_name))
177 {
178 continue;
179 }
180
181 apply_migration(t, migration_name, migration).await?;
183
184 TenantMigration::create(
186 root_t.deref_mut(),
187 CreateTenantMigration {
188 tenant_id: tenant.id,
189 env: tenant.env.clone(),
190 name: migration_name.to_string(),
191 applied_at: Utc::now(),
192 },
193 )
194 .await?;
195 }
196
197 Ok(())
198}
199
200pub async fn apply_root_migrations(
205 root_t: &mut DbTransaction<'_>,
206 target_migration_name: Option<&str>,
207) -> DbResult<()> {
208 let migrations = RootMigration::all(root_t.deref_mut()).await?;
209
210 for (migration_name, migration) in ROOT_MIGRATIONS {
211 if target_migration_name
213 .is_some_and(|target_migration_name| target_migration_name.ne(*migration_name))
214 {
215 continue;
216 }
217
218 if migrations
220 .iter()
221 .any(|migration| migration.name.eq(migration_name))
222 {
223 continue;
224 }
225
226 apply_migration(root_t, migration_name, migration).await?;
228
229 RootMigration::create(
231 root_t.deref_mut(),
232 CreateRootMigration {
233 name: migration_name.to_string(),
234 applied_at: Utc::now(),
235 },
236 )
237 .await?;
238 }
239
240 Ok(())
241}
242
243pub async fn force_apply_tenant_migrations(
247 t: &mut DbTransaction<'_>,
248 target_migration_name: Option<&str>,
249) -> DbResult<()> {
250 for (migration_name, migration) in TENANT_MIGRATIONS {
251 if target_migration_name
253 .is_some_and(|target_migration_name| target_migration_name.ne(*migration_name))
254 {
255 continue;
256 }
257
258 apply_migration(t, migration_name, migration).await?;
259 }
260
261 Ok(())
262}
263
264pub async fn apply_migration(
266 db: &mut DbTransaction<'_>,
267 migration_name: &str,
268 migration: &str,
269) -> DbResult<()> {
270 let queries = migration
272 .split(';')
273 .map(|query| query.trim())
274 .filter(|query| !query.is_empty());
275
276 for query in queries {
277 let result = sqlx::query(query)
278 .execute(db.deref_mut())
279 .await
280 .inspect_err(|error| {
281 eprintln!("{error:?} {query}");
282 tracing::error!(?error, ?migration_name, "failed to perform migration")
283 })?;
284 let rows_affected = result.rows_affected();
285
286 tracing::debug!(?migration_name, ?rows_affected, "applied migration query");
287 }
288
289 Ok(())
290}