bottle_orm/
migration.rs

1//! # Migration Module
2//!
3//! This module provides schema migration management functionality for Bottle ORM.
4//! It handles the registration and execution of database schema changes, including
5//! table creation and foreign key constraint assignment.
6//!
7//! ## Overview
8//!
9//! The migration system follows a two-phase approach:
10//!
11//! 1. **Table Creation Phase**: Creates all registered tables with their columns,
12//!    indexes, and constraints (except foreign keys)
13//! 2. **Foreign Key Phase**: Assigns foreign key constraints after all tables exist
14//!
15//! This ensures that foreign keys can reference tables that haven't been created yet.
16//!
17//! ## Features
18//!
19//! - **Automatic Ordering**: Handles dependencies between tables automatically
20//! - **Idempotent Operations**: Safe to run multiple times (uses IF NOT EXISTS)
21//! - **Type Safety**: Leverages Rust's type system for compile-time validation
22//! - **Async Execution**: Non-blocking migration execution
23//!
24//! ## Example Usage
25//!
26//! ```rust,ignore
27//! use bottle_orm::{Database, Model};
28//! use uuid::Uuid;
29//!
30//! #[derive(Model)]
31//! struct User {
32//!     #[orm(primary_key)]
33//!     id: Uuid,
34//!     username: String,
35//! }
36//!
37//! #[derive(Model)]
38//! struct Post {
39//!     #[orm(primary_key)]
40//!     id: Uuid,
41//!     #[orm(foreign_key = "User::id")]
42//!     user_id: Uuid,
43//!     title: String,
44//! }
45//!
46//! #[tokio::main]
47//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
48//!     let db = Database::connect("postgres://localhost/mydb").await?;
49//!
50//!     // Register and run migrations
51//!     db.migrator()
52//!         .register::<User>()
53//!         .register::<Post>()
54//!         .run()
55//!         .await?;
56//!
57//!     Ok(())
58//! }
59//! ```
60
61// ============================================================================
62// External Crate Imports
63// ============================================================================
64
65use futures::future::BoxFuture;
66
67// ============================================================================
68// Internal Crate Imports
69// ============================================================================
70
71use crate::{database::Database, model::Model};
72
73// ============================================================================
74// Type Aliases
75// ============================================================================
76
77/// Type alias for migration tasks (e.g., Create Table, Add Foreign Key).
78///
79/// Migration tasks are async closures that take a `Database` instance and return
80/// a boxed future that resolves to a Result. This allows for flexible, composable
81/// migration operations.
82///
83/// # Type Definition
84///
85/// ```rust,ignore
86/// type MigrationTask = Box<
87///     dyn Fn(Database) -> BoxFuture<'static, Result<(), sqlx::Error>> + Send + Sync
88/// >;
89/// ```
90///
91/// # Parameters
92///
93/// * `Database` - Cloned database instance for the migration operation
94///
95/// # Returns
96///
97/// * `BoxFuture<'static, Result<(), sqlx::Error>>` - Async result of the migration
98///
99/// # Traits
100///
101/// * `Send` - Can be safely sent between threads
102/// * `Sync` - Can be safely shared between threads
103///
104/// # Example
105///
106/// ```rust,ignore
107/// let task: MigrationTask = Box::new(|db: Database| {
108///     Box::pin(async move {
109///         db.create_table::<User>().await?;
110///         Ok(())
111///     })
112/// });
113/// ```
114pub type MigrationTask = Box<dyn Fn(Database) -> BoxFuture<'static, Result<(), sqlx::Error>> + Send + Sync>;
115
116// ============================================================================
117// Migrator Struct
118// ============================================================================
119
120/// Schema migration manager.
121///
122/// The `Migrator` is responsible for managing and executing database schema migrations.
123/// It maintains two separate task queues: one for table creation and one for foreign
124/// key assignment. This separation ensures that all tables exist before any foreign
125/// keys are created.
126///
127/// # Fields
128///
129/// * `db` - Reference to the database connection
130/// * `tasks` - Queue of table creation tasks
131/// * `fk_task` - Queue of foreign key assignment tasks
132///
133/// # Lifecycle
134///
135/// 1. Create migrator via `Database::migrator()`
136/// 2. Register models via `register::<T>()`
137/// 3. Execute all migrations via `run()`
138///
139/// # Example
140///
141/// ```rust,ignore
142/// use bottle_orm::{Database, Model};
143///
144/// #[derive(Model)]
145/// struct User {
146///     #[orm(primary_key)]
147///     id: i32,
148///     username: String,
149/// }
150///
151/// #[derive(Model)]
152/// struct Post {
153///     #[orm(primary_key)]
154///     id: i32,
155///     #[orm(foreign_key = "User::id")]
156///     user_id: i32,
157///     title: String,
158/// }
159///
160/// let db = Database::connect("sqlite::memory:").await?;
161///
162/// let result = db.migrator()
163///     .register::<User>()
164///     .register::<Post>()
165///     .run()
166///     .await?;
167/// ```
168pub struct Migrator<'a> {
169    /// Reference to the database connection.
170    ///
171    /// This is used to execute migration tasks and is cloned for each task
172    /// to allow async execution without lifetime issues.
173    pub(crate) db: &'a Database,
174
175    /// Queue of table creation tasks.
176    ///
177    /// These tasks are executed first, in the order they were registered.
178    /// Each task creates a table with its columns, indexes, and constraints
179    /// (except foreign keys).
180    pub(crate) tasks: Vec<MigrationTask>,
181
182    /// Queue of foreign key assignment tasks.
183    ///
184    /// These tasks are executed after all table creation tasks complete.
185    /// This ensures that referenced tables exist before foreign keys are created.
186    pub(crate) fk_task: Vec<MigrationTask>,
187}
188
189// ============================================================================
190// Migrator Implementation
191// ============================================================================
192
193impl<'a> Migrator<'a> {
194    // ========================================================================
195    // Constructor
196    // ========================================================================
197
198    /// Creates a new Migrator instance associated with a Database.
199    ///
200    /// This constructor initializes empty task queues for table creation
201    /// and foreign key assignment. Typically called via `Database::migrator()`
202    /// rather than directly.
203    ///
204    /// # Arguments
205    ///
206    /// * `db` - Reference to the database connection
207    ///
208    /// # Returns
209    ///
210    /// A new `Migrator` instance with empty task queues
211    ///
212    /// # Example
213    ///
214    /// ```rust,ignore
215    /// // Usually called via database method
216    /// let migrator = db.migrator();
217    ///
218    /// // Direct construction (rarely needed)
219    /// let migrator = Migrator::new(&db);
220    /// ```
221    pub fn new(db: &'a Database) -> Self {
222        Self { db, tasks: Vec::new(), fk_task: Vec::new() }
223    }
224
225    // ========================================================================
226    // Model Registration
227    // ========================================================================
228
229    /// Registers a Model for migration.
230    ///
231    /// This method queues two tasks for the specified model:
232    ///
233    /// 1. **Table Creation Task**: Creates the table with columns, indexes,
234    ///    and inline constraints (PRIMARY KEY, UNIQUE, NOT NULL, etc.)
235    /// 2. **Foreign Key Task**: Assigns foreign key constraints after all
236    ///    tables are created
237    ///
238    /// Multiple models can be registered by chaining calls to this method.
239    /// The tasks will be executed in the order they were registered.
240    ///
241    /// # Type Parameters
242    ///
243    /// * `T` - The Model type to register. Must implement `Model + Send + Sync + 'static`
244    ///
245    /// # Returns
246    ///
247    /// Returns `self` to enable method chaining
248    ///
249    /// # Example
250    ///
251    /// ```rust,ignore
252    /// use bottle_orm::{Database, Model};
253    /// use uuid::Uuid;
254    ///
255    /// #[derive(Model)]
256    /// struct User {
257    ///     #[orm(primary_key)]
258    ///     id: Uuid,
259    ///     username: String,
260    /// }
261    ///
262    /// #[derive(Model)]
263    /// struct Post {
264    ///     #[orm(primary_key)]
265    ///     id: Uuid,
266    ///     #[orm(foreign_key = "User::id")]
267    ///     user_id: Uuid,
268    ///     title: String,
269    /// }
270    ///
271    /// #[derive(Model)]
272    /// struct Comment {
273    ///     #[orm(primary_key)]
274    ///     id: Uuid,
275    ///     #[orm(foreign_key = "Post::id")]
276    ///     post_id: Uuid,
277    ///     #[orm(foreign_key = "User::id")]
278    ///     user_id: Uuid,
279    ///     content: String,
280    /// }
281    ///
282    /// // Register multiple models
283    /// db.migrator()
284    ///     .register::<User>()      // Creates 'user' table first
285    ///     .register::<Post>()      // Creates 'post' table
286    ///     .register::<Comment>()   // Creates 'comment' table
287    ///     .run()                   // Executes all migrations
288    ///     .await?;
289    /// ```
290    ///
291    /// # Task Execution Order
292    ///
293    /// 1. User table creation
294    /// 2. Post table creation
295    /// 3. Comment table creation
296    /// 4. Post foreign keys (user_id → User.id)
297    /// 5. Comment foreign keys (post_id → Post.id, user_id → User.id)
298    ///
299    /// # See Also
300    ///
301    /// * [`run()`](#method.run) - For executing registered migrations
302    /// * [`Database::create_table()`] - For manual table creation
303    /// * [`Database::assign_foreign_keys()`] - For manual FK assignment
304    pub fn register<T>(mut self) -> Self
305    where
306        T: Model + 'static + Send + Sync,
307    {
308        // Create table creation task
309        // This task clones the database and creates the table asynchronously
310        let task = Box::new(|db: Database| -> BoxFuture<'static, Result<(), sqlx::Error>> {
311            Box::pin(async move {
312                // Create table with columns, indexes, and inline constraints
313                db.create_table::<T>().await?;
314                Ok(())
315            })
316        });
317
318        // Create foreign key assignment task
319        // This task runs after all tables are created to ensure references exist
320        let fk_task = Box::new(|db: Database| -> BoxFuture<'static, Result<(), sqlx::Error>> {
321            Box::pin(async move {
322                // Assign foreign key constraints
323                db.assign_foreign_keys::<T>().await?;
324                Ok(())
325            })
326        });
327
328        // Add tasks to their respective queues
329        self.tasks.push(task);
330        self.fk_task.push(fk_task);
331
332        // Return self for method chaining
333        self
334    }
335
336    // ========================================================================
337    // Migration Execution
338    // ========================================================================
339
340    /// Executes all registered migration tasks.
341    ///
342    /// This method runs all queued migrations in two phases:
343    ///
344    /// **Phase 1: Table Creation**
345    /// - Executes all table creation tasks in registration order
346    /// - Creates tables with columns, indexes, and inline constraints
347    /// - Uses `CREATE TABLE IF NOT EXISTS` for idempotency
348    ///
349    /// **Phase 2: Foreign Key Assignment**
350    /// - Executes all foreign key tasks in registration order
351    /// - Creates foreign key constraints between tables
352    /// - Checks for existing constraints to avoid duplicates
353    ///
354    /// If any task fails, the entire migration is aborted and an error is returned.
355    ///
356    /// # Returns
357    ///
358    /// * `Ok(Database)` - Cloned database instance on success
359    /// * `Err(sqlx::Error)` - Database error during migration
360    ///
361    /// # Error Handling
362    ///
363    /// Errors can occur for various reasons:
364    ///
365    /// - **Connection Errors**: Database connection lost during migration
366    /// - **Syntax Errors**: Invalid SQL generated (shouldn't happen with correct Model definitions)
367    /// - **Permission Errors**: Insufficient database privileges
368    /// - **Constraint Violations**: Existing data violates new constraints
369    ///
370    /// # Example
371    ///
372    /// ```rust,ignore
373    /// use bottle_orm::{Database, Model};
374    ///
375    /// #[derive(Model)]
376    /// struct User {
377    ///     #[orm(primary_key)]
378    ///     id: i32,
379    ///     username: String,
380    /// }
381    ///
382    /// let db = Database::connect("sqlite::memory:").await?;
383    ///
384    /// // Run migrations
385    /// match db.migrator().register::<User>().run().await {
386    ///     Ok(db) => println!("Migrations completed successfully"),
387    ///     Err(e) => eprintln!("Migration failed: {}", e),
388    /// }
389    /// ```
390    ///
391    /// # Idempotency
392    ///
393    /// Migrations are designed to be idempotent and can be run multiple times safely:
394    ///
395    /// ```rust,ignore
396    /// // First run: creates tables
397    /// db.migrator().register::<User>().run().await?;
398    ///
399    /// // Second run: no-op (tables already exist)
400    /// db.migrator().register::<User>().run().await?;
401    /// ```
402    ///
403    /// # Performance Considerations
404    ///
405    /// - Migrations are executed sequentially, not in parallel
406    /// - Large schemas may take time to migrate
407    /// - Consider running migrations during deployment/startup
408    /// - Use database transactions where supported
409    ///
410    /// # See Also
411    ///
412    /// * [`register()`](#method.register) - For registering models
413    /// * [`Database::create_table()`] - For manual table creation
414    /// * [`Database::assign_foreign_keys()`] - For manual FK assignment
415    pub async fn run(self) -> Result<Database, sqlx::Error> {
416        // ====================================================================
417        // Phase 1: Execute Table Creation Tasks
418        // ====================================================================
419        // Create all tables in the order they were registered.
420        // This ensures that models are created before their dependents.
421        for task in self.tasks {
422            // Clone the database for the async task
423            // This is safe because Database contains a connection pool
424            (task)(self.db.clone()).await?;
425        }
426
427        // ====================================================================
428        // Phase 2: Execute Foreign Key Assignment Tasks
429        // ====================================================================
430        // Assign foreign keys after all tables exist.
431        // This prevents errors where a foreign key references a table
432        // that hasn't been created yet.
433        for task in self.fk_task {
434            // Clone the database for the async task
435            (task)(self.db.clone()).await?;
436        }
437
438        // Return cloned database instance for continued use
439        Ok(self.db.clone())
440    }
441}