bottle_orm/migration.rs
1use crate::{database::Database, model::Model};
2use futures::future::BoxFuture;
3
4/// Type alias for migration tasks (e.g., Create Table, Add Foreign Key).
5///
6/// These tasks are closures that take a `Database` connection and return a future.
7pub type MigrationTask = Box<dyn Fn(Database) -> BoxFuture<'static, Result<(), sqlx::Error>> + Send + Sync>;
8
9/// Schema migration manager.
10///
11/// Handles the registration of models and executes table creation and relationship setup in order.
12pub struct Migrator<'a> {
13 pub(crate) db: &'a Database,
14 pub(crate) tasks: Vec<MigrationTask>,
15 pub(crate) fk_task: Vec<MigrationTask>,
16}
17
18impl<'a> Migrator<'a> {
19 /// Creates a new Migrator instance associated with a Database.
20 pub fn new(db: &'a Database) -> Self {
21 Self { db, tasks: Vec::new(), fk_task: Vec::new() }
22 }
23
24 /// Registers a Model for migration.
25 ///
26 /// This queues tasks to:
27 /// 1. Create the table for the model.
28 /// 2. Assign foreign keys (executed later to ensure all tables exist).
29 ///
30 /// # Example
31 ///
32 /// ```rust,ignore
33 /// db.migrator()
34 /// .register::<User>()
35 /// .register::<Post>()
36 /// .run()
37 /// .await?;
38 /// ```
39 pub fn register<T>(mut self) -> Self
40 where
41 T: Model + 'static + Send + Sync,
42 {
43 let task = Box::new(|db: Database| -> BoxFuture<'static, Result<(), sqlx::Error>> {
44 Box::pin(async move {
45 db.create_table::<T>().await?;
46 Ok(())
47 })
48 });
49
50 let fk_task = Box::new(|db: Database| -> BoxFuture<'static, Result<(), sqlx::Error>> {
51 Box::pin(async move {
52 db.assign_foreign_keys::<T>().await?;
53 Ok(())
54 })
55 });
56 self.tasks.push(task);
57 self.fk_task.push(fk_task);
58 self
59 }
60
61 /// Executes all registered migration tasks.
62 ///
63 /// The process follows two steps:
64 /// 1. Creates all tables (executing standard migration tasks).
65 /// 2. Creates all foreign keys (executing foreign key tasks).
66 pub async fn run(self) -> Result<Database, sqlx::Error> {
67 for task in self.tasks {
68 (task)(self.db.clone()).await?;
69 }
70
71 for task in self.fk_task {
72 (task)(self.db.clone()).await?;
73 }
74 Ok(self.db.clone())
75 }
76}