migratio/
migrator.rs

1use crate::error::Error;
2use chrono::Utc;
3use rusqlite::{params, Connection, Transaction};
4use sha2::{Digest, Sha256};
5use std::time::Instant;
6
7/// Represents a failure during a migration.
8#[derive(Debug, PartialEq)]
9pub struct MigrationFailure<'migration> {
10    migration: &'migration Box<dyn Migration>,
11    error: Error,
12}
13
14impl<'migration> MigrationFailure<'migration> {
15    /// Get the migration that failed.
16    pub fn migration(&self) -> &dyn Migration {
17        self.migration.as_ref()
18    }
19
20    /// Get the error that caused the migration to fail.
21    pub fn error(&self) -> &Error {
22        &self.error
23    }
24}
25
26/// A report of actions performed during a migration.
27#[derive(Debug, PartialEq)]
28pub struct MigrationReport<'migration> {
29    pub schema_version_table_existed: bool,
30    pub schema_version_table_created: bool,
31    pub migrations_run: Vec<u32>,
32    pub failing_migration: Option<MigrationFailure<'migration>>,
33}
34
35/// Represents the result of a migration precondition check.
36#[derive(Debug, Clone, PartialEq)]
37pub enum Precondition {
38    /// The migration's changes are already present in the database and should be stamped without running up().
39    AlreadySatisfied,
40    /// The migration needs to be applied by running up().
41    NeedsApply,
42}
43
44/// Represents a migration that has been applied to the database.
45#[derive(Debug, Clone, PartialEq)]
46pub struct AppliedMigration {
47    /// The version number of the migration.
48    pub version: u32,
49    /// The name of the migration.
50    pub name: String,
51    /// The timestamp when the migration was applied.
52    pub applied_at: chrono::DateTime<Utc>,
53    /// The checksum of the migration at the time it was applied.
54    pub checksum: String,
55}
56
57const DEFAULT_VERSION_TABLE_NAME: &str = "_migratio_version_";
58
59/// A trait that must be implemented to define a migration.
60/// The `version` value must be unique among all migrations supplied to the migrator, and greater than 0.
61/// Implement your migration logic in the `up` method, using the supplied [Transaction] to perform database operations.
62/// The transaction will be automatically committed if the migration succeeds, or rolled back if it fails.
63/// Optionally implement the `down` method to enable rollback support via [SqliteMigrator::downgrade].
64/// The `name` and `description` methods are optional, and only aid in debugging / observability.
65pub trait Migration {
66    /// Returns the version number of this migration.
67    ///
68    /// # IMPORTANT WARNING
69    ///
70    /// **Once a migration has been applied to any database, its version number must NEVER be changed.**
71    /// The version is used to track which migrations have been applied. Changing it will cause
72    /// the migrator to fail validation with an error about missing or orphaned migrations.
73    ///
74    /// # Requirements
75    ///
76    /// - Must be greater than 0
77    /// - Must be unique across all migrations
78    /// - Must be contiguous (1, 2, 3, ... with no gaps)
79    /// - Must be immutable once the migration is applied to any database
80    fn version(&self) -> u32;
81
82    fn up(&self, tx: &Transaction) -> Result<(), Error>;
83
84    /// Rollback this migration. This is optional - the default implementation panics.
85    /// If you want to support downgrade functionality, implement this method.
86    fn down(&self, _tx: &Transaction) -> Result<(), Error> {
87        panic!(
88            "Migration {} ('{}') does not support downgrade. Implement the down() method to enable rollback.",
89            self.version(),
90            self.name()
91        )
92    }
93
94    /// Returns the name of this migration.
95    ///
96    /// # IMPORTANT WARNING
97    ///
98    /// **Once a migration has been applied to any database, its name must NEVER be changed.**
99    /// The name is included in the checksum used to verify migration integrity. Changing it
100    /// will cause the migrator to fail with a checksum mismatch error.
101    ///
102    /// The default implementation returns "Migration {version}". You can override this to
103    /// provide a more descriptive name, but remember: once applied, it's permanent.
104    fn name(&self) -> String {
105        format!("Migration {}", self.version())
106    }
107
108    /// Returns an optional description of what this migration does.
109    ///
110    /// Unlike `version()` and `name()`, the description can be changed at any time as it's
111    /// not used for migration tracking or validation. Use this for human-readable documentation.
112    fn description(&self) -> Option<&'static str> {
113        None
114    }
115
116    /// Optional precondition check for the migration.
117    ///
118    /// This method is called before running the migration's `up()` method during upgrade.
119    /// It allows the migration to check if its changes are already present in the database
120    /// (for example, when adopting migratio after using another migration tool).
121    ///
122    /// If this returns `Precondition::AlreadySatisfied`, the migration is stamped as applied
123    /// in the version table without running `up()`. If it returns `Precondition::NeedsApply`,
124    /// the migration runs normally via `up()`.
125    ///
126    /// The default implementation returns `Precondition::NeedsApply`, meaning migrations
127    /// always run unless you override this method.
128    ///
129    /// # Example
130    /// ```
131    /// use migratio::{Migration, Precondition, Error};
132    /// use rusqlite::Transaction;
133    ///
134    /// struct Migration1;
135    /// impl Migration for Migration1 {
136    ///     fn version(&self) -> u32 { 1 }
137    ///
138    ///     fn up(&self, tx: &Transaction) -> Result<(), Error> {
139    ///         tx.execute("CREATE TABLE users (id INTEGER PRIMARY KEY)", [])?;
140    ///         Ok(())
141    ///     }
142    ///
143    ///     fn precondition(&self, tx: &Transaction) -> Result<Precondition, Error> {
144    ///         // Check if the table already exists
145    ///         let mut stmt = tx.prepare(
146    ///             "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='users'"
147    ///         )?;
148    ///         let count: i64 = stmt.query_row([], |row| row.get(0))?;
149    ///
150    ///         if count > 0 {
151    ///             Ok(Precondition::AlreadySatisfied)
152    ///         } else {
153    ///             Ok(Precondition::NeedsApply)
154    ///         }
155    ///     }
156    /// }
157    /// ```
158    fn precondition(&self, _tx: &Transaction) -> Result<Precondition, Error> {
159        Ok(Precondition::NeedsApply)
160    }
161}
162
163impl PartialEq for dyn Migration {
164    fn eq(&self, other: &Self) -> bool {
165        self.version() == other.version()
166    }
167}
168
169impl std::fmt::Debug for dyn Migration {
170    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171        f.debug_struct("Migration")
172            .field("version", &self.version())
173            .field("name", &self.name())
174            .finish()
175    }
176}
177
178/// The entrypoint for running a sequence of [Migration]s.
179/// Construct this struct with the list of all [Migration]s to be applied.
180/// [Migration::version]s must be contiguous, greater than zero, and unique.
181pub struct SqliteMigrator {
182    migrations: Vec<Box<dyn Migration>>,
183    schema_version_table_name: String,
184    busy_timeout: std::time::Duration,
185    on_migration_start: Option<Box<dyn Fn(u32, &str) + Send + Sync>>,
186    on_migration_complete: Option<Box<dyn Fn(u32, &str, std::time::Duration) + Send + Sync>>,
187    on_migration_skipped: Option<Box<dyn Fn(u32, &str) + Send + Sync>>,
188    on_migration_error: Option<Box<dyn Fn(u32, &str, &Error) + Send + Sync>>,
189}
190
191// Manual Debug impl since closures don't implement Debug
192impl std::fmt::Debug for SqliteMigrator {
193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        f.debug_struct("SqliteMigrator")
195            .field("migrations", &self.migrations)
196            .field("schema_version_table_name", &self.schema_version_table_name)
197            .field("busy_timeout", &self.busy_timeout)
198            .field("on_migration_start", &self.on_migration_start.is_some())
199            .field(
200                "on_migration_complete",
201                &self.on_migration_complete.is_some(),
202            )
203            .field("on_migration_skipped", &self.on_migration_skipped.is_some())
204            .field("on_migration_error", &self.on_migration_error.is_some())
205            .finish()
206    }
207}
208
209impl SqliteMigrator {
210    /// Set up connection for safe concurrent access.
211    /// This sets a busy timeout so concurrent operations wait instead of failing immediately.
212    fn setup_concurrency_protection(&self, conn: &Connection) -> Result<(), Error> {
213        // Set the configured busy timeout
214        // This makes concurrent migration attempts wait instead of failing immediately
215        conn.busy_timeout(self.busy_timeout)?;
216        Ok(())
217    }
218
219    /// Calculate a checksum for a migration based on its version and name.
220    /// This is used to verify that migrations haven't been modified after being applied.
221    fn calculate_checksum(migration: &Box<dyn Migration>) -> String {
222        let mut hasher = Sha256::new();
223        hasher.update(migration.version().to_string().as_bytes());
224        hasher.update(b"|");
225        hasher.update(migration.name().as_bytes());
226        format!("{:x}", hasher.finalize())
227    }
228
229    /// Create a new SqliteMigrator, validating migration invariants.
230    /// Returns an error if migrations are invalid.
231    pub fn try_new(migrations: Vec<Box<dyn Migration>>) -> Result<Self, String> {
232        // Verify invariants
233        let mut versions: Vec<u32> = migrations.iter().map(|m| m.version()).collect();
234        versions.sort();
235
236        // Check for duplicates and zero versions
237        for (i, &version) in versions.iter().enumerate() {
238            // Version must be greater than zero
239            if version == 0 {
240                return Err("Migration version must be greater than 0, found version 0".to_string());
241            }
242
243            // Check for duplicates
244            if i > 0 && versions[i - 1] == version {
245                return Err(format!("Duplicate migration version found: {}", version));
246            }
247        }
248
249        // Check for contiguity (versions must be 1, 2, 3, ...)
250        if !versions.is_empty() {
251            if versions[0] != 1 {
252                return Err(format!(
253                    "Migration versions must start at 1, found starting version: {}",
254                    versions[0]
255                ));
256            }
257
258            for (i, &version) in versions.iter().enumerate() {
259                let expected = (i + 1) as u32;
260                if version != expected {
261                    return Err(format!(
262                        "Migration versions must be contiguous. Expected version {}, found {}",
263                        expected, version
264                    ));
265                }
266            }
267        }
268
269        Ok(Self {
270            migrations,
271            schema_version_table_name: DEFAULT_VERSION_TABLE_NAME.to_string(),
272            busy_timeout: std::time::Duration::from_secs(30),
273            on_migration_start: None,
274            on_migration_complete: None,
275            on_migration_skipped: None,
276            on_migration_error: None,
277        })
278    }
279
280    /// Create a new SqliteMigrator, panicking if migration metadata is invalid.
281    /// For a non-panicking version, use `try_new`.
282    pub fn new(migrations: Vec<Box<dyn Migration>>) -> Self {
283        match Self::try_new(migrations) {
284            Ok(migrator) => migrator,
285            Err(err) => panic!("{}", err),
286        }
287    }
288
289    /// Set a custom name for the schema version tracking table.
290    /// Defaults to "_migratio_version_".
291    pub fn with_schema_version_table_name(mut self, name: impl Into<String>) -> Self {
292        self.schema_version_table_name = name.into();
293        self
294    }
295
296    /// Set the busy timeout for SQLite database operations.
297    /// This controls how long concurrent migration attempts will wait for locks.
298    /// Defaults to 30 seconds.
299    pub fn with_busy_timeout(mut self, timeout: std::time::Duration) -> Self {
300        self.busy_timeout = timeout;
301        self
302    }
303
304    /// Set a callback to be invoked when a migration starts.
305    /// The callback receives the migration version and name.
306    ///
307    /// # Example
308    /// ```
309    /// use migratio::SqliteMigrator;
310    /// # use migratio::Migration;
311    /// # use rusqlite::Transaction;
312    /// # use migratio::Error;
313    ///
314    /// # struct Migration1;
315    /// # impl Migration for Migration1 {
316    /// #     fn version(&self) -> u32 { 1 }
317    /// #     fn up(&self, tx: &Transaction) -> Result<(), Error> { Ok(()) }
318    /// # }
319    /// let migrator = SqliteMigrator::new(vec![Box::new(Migration1)])
320    ///     .on_migration_start(|version, name| {
321    ///         println!("Starting migration {} ({})", version, name);
322    ///     });
323    /// ```
324    pub fn on_migration_start<F>(mut self, callback: F) -> Self
325    where
326        F: Fn(u32, &str) + Send + Sync + 'static,
327    {
328        self.on_migration_start = Some(Box::new(callback));
329        self
330    }
331
332    /// Set a callback to be invoked when a migration completes successfully.
333    /// The callback receives the migration version, name, and duration.
334    ///
335    /// # Example
336    /// ```
337    /// use migratio::SqliteMigrator;
338    /// # use migratio::Migration;
339    /// # use rusqlite::Transaction;
340    /// # use migratio::Error;
341    ///
342    /// # struct Migration1;
343    /// # impl Migration for Migration1 {
344    /// #     fn version(&self) -> u32 { 1 }
345    /// #     fn up(&self, tx: &Transaction) -> Result<(), Error> { Ok(()) }
346    /// # }
347    /// let migrator = SqliteMigrator::new(vec![Box::new(Migration1)])
348    ///     .on_migration_complete(|version, name, duration| {
349    ///         println!("Migration {} ({}) completed in {:?}", version, name, duration);
350    ///     });
351    /// ```
352    pub fn on_migration_complete<F>(mut self, callback: F) -> Self
353    where
354        F: Fn(u32, &str, std::time::Duration) + Send + Sync + 'static,
355    {
356        self.on_migration_complete = Some(Box::new(callback));
357        self
358    }
359
360    /// Set a callback to be invoked when a migration is skipped because its precondition
361    /// returned `Precondition::AlreadySatisfied`.
362    /// The callback receives the migration version and name.
363    ///
364    /// # Example
365    /// ```
366    /// use migratio::SqliteMigrator;
367    /// # use migratio::Migration;
368    /// # use rusqlite::Transaction;
369    /// # use migratio::Error;
370    ///
371    /// # struct Migration1;
372    /// # impl Migration for Migration1 {
373    /// #     fn version(&self) -> u32 { 1 }
374    /// #     fn up(&self, tx: &Transaction) -> Result<(), Error> { Ok(()) }
375    /// # }
376    /// let migrator = SqliteMigrator::new(vec![Box::new(Migration1)])
377    ///     .on_migration_skipped(|version, name| {
378    ///         println!("Migration {} ({}) skipped - already satisfied", version, name);
379    ///     });
380    /// ```
381    pub fn on_migration_skipped<F>(mut self, callback: F) -> Self
382    where
383        F: Fn(u32, &str) + Send + Sync + 'static,
384    {
385        self.on_migration_skipped = Some(Box::new(callback));
386        self
387    }
388
389    /// Set a callback to be invoked when a migration fails.
390    /// The callback receives the migration version, name, and error.
391    ///
392    /// # Example
393    /// ```
394    /// use migratio::SqliteMigrator;
395    /// # use migratio::Migration;
396    /// # use rusqlite::Transaction;
397    /// # use migratio::Error;
398    ///
399    /// # struct Migration1;
400    /// # impl Migration for Migration1 {
401    /// #     fn version(&self) -> u32 { 1 }
402    /// #     fn up(&self, tx: &Transaction) -> Result<(), Error> { Ok(()) }
403    /// # }
404    /// let migrator = SqliteMigrator::new(vec![Box::new(Migration1)])
405    ///     .on_migration_error(|version, name, error| {
406    ///         eprintln!("Migration {} ({}) failed: {:?}", version, name, error);
407    ///     });
408    /// ```
409    pub fn on_migration_error<F>(mut self, callback: F) -> Self
410    where
411        F: Fn(u32, &str, &Error) + Send + Sync + 'static,
412    {
413        self.on_migration_error = Some(Box::new(callback));
414        self
415    }
416
417    /// Get a reference to all migrations in this migrator.
418    pub fn migrations(&self) -> &[Box<dyn Migration>] {
419        &self.migrations
420    }
421
422    /// Get the current migration version from the database.
423    /// Returns 0 if no migrations have been applied.
424    pub fn get_current_version(&self, conn: &mut Connection) -> Result<u32, Error> {
425        // Check if schema version table exists
426        let mut stmt =
427            conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?1")?;
428        let table_exists = stmt
429            .query([&self.schema_version_table_name])?
430            .next()?
431            .is_some();
432
433        if !table_exists {
434            return Ok(0);
435        }
436
437        // Get current version (highest version number)
438        let mut stmt = conn.prepare(&format!(
439            "SELECT MAX(version) from {}",
440            self.schema_version_table_name
441        ))?;
442        let version: Option<u32> = stmt.query_row([], |row| row.get(0))?;
443        Ok(version.unwrap_or(0))
444    }
445
446    /// Get the history of all migrations that have been applied to the database.
447    /// Returns migrations in the order they were applied (by version number).
448    /// Returns an empty vector if no migrations have been applied.
449    pub fn get_migration_history(
450        &self,
451        conn: &mut Connection,
452    ) -> Result<Vec<AppliedMigration>, Error> {
453        // Check if schema version table exists
454        let mut stmt =
455            conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?1")?;
456        let table_exists = stmt
457            .query([&self.schema_version_table_name])?
458            .next()?
459            .is_some();
460
461        if !table_exists {
462            return Ok(vec![]);
463        }
464
465        // Query all applied migrations, ordered by version
466        let mut stmt = conn.prepare(&format!(
467            "SELECT version, name, applied_at, checksum FROM {} ORDER BY version",
468            self.schema_version_table_name
469        ))?;
470
471        let migrations = stmt
472            .query_map([], |row| {
473                let applied_at_str: String = row.get(2)?;
474                let applied_at = chrono::DateTime::parse_from_rfc3339(&applied_at_str)
475                    .map_err(|e| {
476                        rusqlite::Error::FromSqlConversionFailure(
477                            2,
478                            rusqlite::types::Type::Text,
479                            Box::new(e),
480                        )
481                    })?
482                    .with_timezone(&Utc);
483
484                Ok(AppliedMigration {
485                    version: row.get(0)?,
486                    name: row.get(1)?,
487                    applied_at,
488                    checksum: row.get(3)?,
489                })
490            })?
491            .collect::<Result<Vec<_>, _>>()?;
492
493        Ok(migrations)
494    }
495
496    /// Preview which migrations would be applied by `upgrade()` without actually running them.
497    /// Returns a list of migrations that would be executed, in the order they would run.
498    pub fn preview_upgrade(
499        &self,
500        conn: &mut Connection,
501    ) -> Result<Vec<&Box<dyn Migration>>, Error> {
502        let current_version = self.get_current_version(conn)?;
503
504        let mut pending_migrations = self
505            .migrations
506            .iter()
507            .filter(|m| m.version() > current_version)
508            .collect::<Vec<_>>();
509        pending_migrations.sort_by_key(|m| m.version());
510
511        Ok(pending_migrations)
512    }
513
514    /// Preview which migrations would be rolled back by `downgrade(target_version)` without actually running them.
515    /// Returns a list of migrations that would be executed, in the order they would run (reverse order).
516    pub fn preview_downgrade(
517        &self,
518        conn: &mut Connection,
519        target_version: u32,
520    ) -> Result<Vec<&Box<dyn Migration>>, Error> {
521        let current_version = self.get_current_version(conn)?;
522
523        // Validate target version
524        if target_version > current_version {
525            return Err(Error::Rusqlite(rusqlite::Error::InvalidParameterName(
526                format!(
527                    "Cannot downgrade to version {} when current version is {}. Target must be <= current version.",
528                    target_version, current_version
529                ),
530            )));
531        }
532
533        let mut migrations_to_rollback = self
534            .migrations
535            .iter()
536            .filter(|m| m.version() > target_version && m.version() <= current_version)
537            .collect::<Vec<_>>();
538        migrations_to_rollback.sort_by_key(|m| std::cmp::Reverse(m.version())); // Reverse order
539
540        Ok(migrations_to_rollback)
541    }
542
543    /// Apply all previously-unapplied [Migration]s to the database with the given [Connection].
544    /// Each migration runs within its own transaction, which is automatically rolled back if the migration fails.
545    /// This method uses SQLite's busy timeout to handle concurrent migration attempts safely.
546    /// Upgrade the database to a specific target version.
547    ///
548    /// This runs all pending migrations up to and including the target version.
549    /// If the database is already at or beyond the target version, no migrations are run.
550    pub fn upgrade_to(
551        &self,
552        conn: &mut Connection,
553        target_version: u32,
554    ) -> Result<MigrationReport<'_>, Error> {
555        // Validate target version exists
556        if target_version > 0
557            && !self
558                .migrations
559                .iter()
560                .any(|m| m.version() == target_version)
561        {
562            return Err(Error::Rusqlite(rusqlite::Error::InvalidParameterName(
563                format!(
564                    "Target version {} does not exist in migration list",
565                    target_version
566                ),
567            )));
568        }
569
570        self.upgrade_internal(conn, Some(target_version))
571    }
572
573    /// Upgrade the database by running all pending migrations.
574    pub fn upgrade(&self, conn: &mut Connection) -> Result<MigrationReport<'_>, Error> {
575        self.upgrade_internal(conn, None)
576    }
577
578    fn upgrade_internal(
579        &self,
580        conn: &mut Connection,
581        target_version: Option<u32>,
582    ) -> Result<MigrationReport<'_>, Error> {
583        // Set up concurrency protection
584        self.setup_concurrency_protection(conn)?;
585        // if schema version tracking table does not exist, create it
586        let schema_version_table_existed = {
587            let mut stmt =
588                conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?1")?;
589            let schema_version_table_existed = stmt
590                .query([&self.schema_version_table_name])?
591                .next()?
592                .is_some();
593            if !schema_version_table_existed {
594                // create table with name and checksum columns
595                // Use IF NOT EXISTS to handle concurrent creation attempts
596                conn.execute(
597                &format!(
598                    "CREATE TABLE IF NOT EXISTS {} (version integer primary key not null, name text not null, applied_at text not null, checksum text not null)",
599                    self.schema_version_table_name
600                ),
601                [],)?;
602            }
603            schema_version_table_existed
604        };
605
606        // Validate checksums of previously-applied migrations
607        if schema_version_table_existed {
608            // Check if the checksum column exists (for backwards compatibility)
609            let has_checksum_column = {
610                let mut stmt = conn.prepare(&format!(
611                    "PRAGMA table_info({})",
612                    self.schema_version_table_name
613                ))?;
614                let columns: Vec<String> = stmt
615                    .query_map([], |row| row.get::<_, String>(1))?
616                    .collect::<Result<Vec<_>, _>>()?;
617                columns.contains(&"checksum".to_string())
618            };
619
620            if has_checksum_column {
621                // Get all applied migrations with their checksums
622                let mut stmt = conn.prepare(&format!(
623                    "SELECT version, name, checksum FROM {}",
624                    self.schema_version_table_name
625                ))?;
626                let applied_migrations: Vec<(u32, String, String)> = stmt
627                    .query_map([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))?
628                    .collect::<Result<Vec<_>, _>>()?;
629
630                // Verify checksums match for migrations that were already applied
631                // Also detect missing migrations (in DB but not in code)
632                for (applied_version, applied_name, applied_checksum) in &applied_migrations {
633                    if let Some(migration) = self
634                        .migrations
635                        .iter()
636                        .find(|m| m.version() == *applied_version)
637                    {
638                        let current_checksum = Self::calculate_checksum(migration);
639                        if current_checksum != *applied_checksum {
640                            return Err(Error::Rusqlite(rusqlite::Error::InvalidParameterName(
641                                format!(
642                                    "Migration {} checksum mismatch. Expected '{}' but found '{}'. \
643                                    Migration name in DB: '{}', current name: '{}'. \
644                                    This indicates the migration was modified after being applied.",
645                                    applied_version,
646                                    applied_checksum,
647                                    current_checksum,
648                                    applied_name,
649                                    migration.name()
650                                ),
651                            )));
652                        }
653                    } else {
654                        // Migration exists in database but not in code - this is a missing migration
655                        return Err(Error::Rusqlite(rusqlite::Error::InvalidParameterName(
656                            format!(
657                                "Migration {} ('{}') was previously applied but is no longer present in the migration list. \
658                                Applied migrations cannot be removed from the codebase.",
659                                applied_version,
660                                applied_name
661                            ),
662                        )));
663                    }
664                }
665
666                // Detect orphaned migrations (in code but applied migrations are not contiguous)
667                // For example: DB has [1, 2, 4] but code has [1, 2, 3, 4]
668                // This means migration 3 was added after migration 4 was already applied
669                let applied_versions: Vec<u32> =
670                    applied_migrations.iter().map(|(v, _, _)| *v).collect();
671                if !applied_versions.is_empty() {
672                    let max_applied = *applied_versions.iter().max().unwrap();
673
674                    // Check if all migrations up to max_applied exist in the database
675                    for expected_version in 1..=max_applied {
676                        if !applied_versions.contains(&expected_version) {
677                            // There's a gap - check if this migration exists in our code
678                            if let Some(missing_migration) = self
679                                .migrations
680                                .iter()
681                                .find(|m| m.version() == expected_version)
682                            {
683                                return Err(Error::Rusqlite(rusqlite::Error::InvalidParameterName(
684                                    format!(
685                                        "Migration {} ('{}') exists in code but was not applied, yet later migrations are already applied. \
686                                        This likely means migration {} was added after migration {} was already applied. \
687                                        Applied migrations: {:?}",
688                                        expected_version,
689                                        missing_migration.name(),
690                                        expected_version,
691                                        max_applied,
692                                        applied_versions
693                                    ),
694                                )));
695                            }
696                        }
697                    }
698                }
699            }
700        }
701
702        // get current migration version (highest version number)
703        let current_version: u32 = {
704            let mut stmt = conn.prepare(&format!(
705                "SELECT MAX(version) from {}",
706                self.schema_version_table_name
707            ))?;
708            let version: Option<u32> = stmt.query_row([], |row| row.get(0))?;
709            version.unwrap_or(0)
710        };
711        // iterate through migrations, run those that haven't been run
712        let mut migrations_run: Vec<u32> = Vec::new();
713        let mut migrations_sorted = self
714            .migrations
715            .iter()
716            .map(|x| (x.version(), x))
717            .collect::<Vec<_>>();
718        migrations_sorted.sort_by_key(|m| m.0);
719        let mut failing_migration: Option<MigrationFailure> = None;
720        let mut schema_version_table_created = false;
721        // Track the applied_at time for this upgrade() call - all migrations in this batch get the same timestamp
722        let batch_applied_at = Utc::now().to_rfc3339();
723
724        #[cfg(feature = "tracing")]
725        tracing::debug!(
726            current_version = current_version,
727            target_version = ?target_version,
728            available_migrations = ?migrations_sorted.iter().map(|(v, m)| (*v, m.name())).collect::<Vec<_>>(),
729            "Considering migrations to run"
730        );
731
732        for (migration_version, migration) in migrations_sorted {
733            // Stop if we've reached the target version (if specified)
734            if let Some(target) = target_version {
735                if migration_version > target {
736                    #[cfg(feature = "tracing")]
737                    tracing::debug!(
738                        migration_version = migration_version,
739                        target_version = target,
740                        "Skipping migration (beyond target version)"
741                    );
742                    break;
743                }
744            }
745
746            if current_version < migration_version {
747                #[cfg(feature = "tracing")]
748                tracing::debug!(
749                    migration_version = migration_version,
750                    migration_name = %migration.name(),
751                    "Migration needs to be applied"
752                );
753                #[cfg(feature = "tracing")]
754                let _span = tracing::info_span!(
755                    "migration_up",
756                    version = migration_version,
757                    name = %migration.name()
758                )
759                .entered();
760
761                #[cfg(feature = "tracing")]
762                tracing::info!("Starting migration");
763
764                // Call on_migration_start hook
765                if let Some(ref callback) = self.on_migration_start {
766                    callback(migration_version, &migration.name());
767                }
768
769                let migration_start = Instant::now();
770
771                // Run migration or stamp if precondition is satisfied
772                // We wrap this in a block to ensure the transaction is dropped before we use conn
773                let migration_result = {
774                    // Start a transaction for this migration
775                    let tx = conn.transaction()?;
776
777                    // Check precondition
778                    let precondition = match migration.precondition(&tx) {
779                        Ok(p) => p,
780                        Err(error) => {
781                            #[cfg(feature = "tracing")]
782                            tracing::error!(
783                                error = %error,
784                                "Precondition check failed"
785                            );
786
787                            // Call on_migration_error hook
788                            if let Some(ref callback) = self.on_migration_error {
789                                callback(migration_version, &migration.name(), &error);
790                            }
791
792                            // Transaction will be automatically rolled back when dropped
793                            failing_migration = Some(MigrationFailure { migration, error });
794                            break;
795                        }
796                    };
797
798                    match precondition {
799                        Precondition::AlreadySatisfied => {
800                            #[cfg(feature = "tracing")]
801                            tracing::info!("Precondition already satisfied, stamping migration without running up()");
802
803                            // Call on_migration_skipped hook
804                            if let Some(ref callback) = self.on_migration_skipped {
805                                callback(migration_version, &migration.name());
806                            }
807
808                            // Commit the transaction (even though we didn't run up())
809                            tx.commit()?;
810                            Ok(())
811                        }
812                        Precondition::NeedsApply => {
813                            let result = migration.up(&tx);
814                            if result.is_ok() {
815                                // Commit the transaction
816                                tx.commit()?;
817                            }
818                            result
819                        }
820                    }
821                };
822
823                match migration_result {
824                    Ok(_) => {
825                        let migration_duration = migration_start.elapsed();
826
827                        #[cfg(feature = "tracing")]
828                        tracing::info!(
829                            duration_ms = migration_duration.as_millis(),
830                            "Migration completed successfully"
831                        );
832
833                        // Calculate checksum for this migration
834                        let checksum = Self::calculate_checksum(migration);
835
836                        // Insert a row for this migration with its name, timestamp, and checksum
837                        conn.execute(
838                            &format!(
839                                "INSERT INTO {} (version, name, applied_at, checksum) VALUES(?1, ?2, ?3, ?4)",
840                                self.schema_version_table_name
841                            ),
842                            params![migration_version, migration.name(), &batch_applied_at, checksum],
843                        )?;
844
845                        // record migration as run
846                        migrations_run.push(migration_version);
847                        // also, since any migration succeeded, if schema version table had not originally existed,
848                        // we can mark that it was created
849                        schema_version_table_created = true;
850
851                        // Call on_migration_complete hook
852                        if let Some(ref callback) = self.on_migration_complete {
853                            callback(migration_version, &migration.name(), migration_duration);
854                        }
855                    }
856                    Err(e) => {
857                        #[cfg(feature = "tracing")]
858                        tracing::error!(
859                            error = %e,
860                            "Migration failed"
861                        );
862
863                        // Call on_migration_error hook
864                        if let Some(ref callback) = self.on_migration_error {
865                            callback(migration_version, &migration.name(), &e);
866                        }
867
868                        // Transaction will be automatically rolled back when dropped
869                        failing_migration = Some(MigrationFailure {
870                            migration,
871                            error: e,
872                        });
873                        break;
874                    }
875                }
876            } else {
877                #[cfg(feature = "tracing")]
878                tracing::debug!(
879                    migration_version = migration_version,
880                    current_version = current_version,
881                    "Skipping migration (already applied)"
882                );
883            }
884        }
885        // return report
886        Ok(MigrationReport {
887            schema_version_table_existed,
888            schema_version_table_created,
889            failing_migration,
890            migrations_run,
891        })
892    }
893
894    /// Rollback migrations down to the specified target version.
895    /// Pass `target_version = 0` to rollback all migrations.
896    /// Each migration's `down()` method runs within its own transaction, which is automatically rolled back if it fails.
897    /// Returns a [MigrationReport] describing which migrations were rolled back.
898    /// This method uses SQLite's busy timeout to handle concurrent migration attempts safely.
899    pub fn downgrade(
900        &self,
901        conn: &mut Connection,
902        target_version: u32,
903    ) -> Result<MigrationReport<'_>, Error> {
904        // Set up concurrency protection
905        self.setup_concurrency_protection(conn)?;
906        // Check if schema version table exists
907        let schema_version_table_existed = {
908            let mut stmt =
909                conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?1")?;
910            let exists = stmt
911                .query([&self.schema_version_table_name])?
912                .next()?
913                .is_some();
914            exists
915        };
916
917        if !schema_version_table_existed {
918            // No migrations have been applied yet
919            return Ok(MigrationReport {
920                schema_version_table_existed: false,
921                schema_version_table_created: false,
922                failing_migration: None,
923                migrations_run: vec![],
924            });
925        }
926
927        // Validate checksums of previously-applied migrations (same as upgrade)
928        let has_checksum_column = {
929            let mut stmt = conn.prepare(&format!(
930                "PRAGMA table_info({})",
931                self.schema_version_table_name
932            ))?;
933            let columns: Vec<String> = stmt
934                .query_map([], |row| row.get::<_, String>(1))?
935                .collect::<Result<Vec<_>, _>>()?;
936            columns.contains(&"checksum".to_string())
937        };
938
939        if has_checksum_column {
940            let mut stmt = conn.prepare(&format!(
941                "SELECT version, name, checksum FROM {}",
942                self.schema_version_table_name
943            ))?;
944            let applied_migrations: Vec<(u32, String, String)> = stmt
945                .query_map([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))?
946                .collect::<Result<Vec<_>, _>>()?;
947
948            // Verify checksums and detect missing migrations
949            for (applied_version, applied_name, applied_checksum) in &applied_migrations {
950                if let Some(migration) = self
951                    .migrations
952                    .iter()
953                    .find(|m| m.version() == *applied_version)
954                {
955                    let current_checksum = Self::calculate_checksum(migration);
956                    if current_checksum != *applied_checksum {
957                        return Err(Error::Rusqlite(rusqlite::Error::InvalidParameterName(
958                            format!(
959                                "Migration {} checksum mismatch. Expected '{}' but found '{}'. \
960                                Migration name in DB: '{}', current name: '{}'. \
961                                This indicates the migration was modified after being applied.",
962                                applied_version,
963                                applied_checksum,
964                                current_checksum,
965                                applied_name,
966                                migration.name()
967                            ),
968                        )));
969                    }
970                } else {
971                    // Migration exists in database but not in code
972                    return Err(Error::Rusqlite(rusqlite::Error::InvalidParameterName(
973                        format!(
974                            "Migration {} ('{}') was previously applied but is no longer present in the migration list. \
975                            Applied migrations cannot be removed from the codebase.",
976                            applied_version,
977                            applied_name
978                        ),
979                    )));
980                }
981            }
982
983            // Detect orphaned migrations (gaps in applied migrations with code present)
984            let applied_versions: Vec<u32> =
985                applied_migrations.iter().map(|(v, _, _)| *v).collect();
986            if !applied_versions.is_empty() {
987                let max_applied = *applied_versions.iter().max().unwrap();
988
989                for expected_version in 1..=max_applied {
990                    if !applied_versions.contains(&expected_version) {
991                        if let Some(missing_migration) = self
992                            .migrations
993                            .iter()
994                            .find(|m| m.version() == expected_version)
995                        {
996                            return Err(Error::Rusqlite(rusqlite::Error::InvalidParameterName(
997                                format!(
998                                    "Migration {} ('{}') exists in code but was not applied, yet later migrations are already applied. \
999                                    This likely means migration {} was added after migration {} was already applied. \
1000                                    Applied migrations: {:?}",
1001                                    expected_version,
1002                                    missing_migration.name(),
1003                                    expected_version,
1004                                    max_applied,
1005                                    applied_versions
1006                                ),
1007                            )));
1008                        }
1009                    }
1010                }
1011            }
1012        }
1013
1014        // Get current version
1015        let current_version: u32 = {
1016            let mut stmt = conn.prepare(&format!(
1017                "SELECT MAX(version) from {}",
1018                self.schema_version_table_name
1019            ))?;
1020            let version: Option<u32> = stmt.query_row([], |row| row.get(0))?;
1021            version.unwrap_or(0)
1022        };
1023
1024        // Validate target version
1025        if target_version > current_version {
1026            return Err(Error::Rusqlite(rusqlite::Error::InvalidParameterName(
1027                format!(
1028                    "Cannot downgrade to version {} when current version is {}. Target must be <= current version.",
1029                    target_version, current_version
1030                ),
1031            )));
1032        }
1033
1034        // Get migrations to rollback (in reverse order)
1035        let mut migrations_to_rollback = self
1036            .migrations
1037            .iter()
1038            .filter(|m| m.version() > target_version && m.version() <= current_version)
1039            .map(|x| (x.version(), x))
1040            .collect::<Vec<_>>();
1041        migrations_to_rollback.sort_by_key(|m| std::cmp::Reverse(m.0)); // Reverse order
1042
1043        let mut migrations_run: Vec<u32> = Vec::new();
1044        let mut failing_migration: Option<MigrationFailure> = None;
1045
1046        for (migration_version, migration) in migrations_to_rollback {
1047            #[cfg(feature = "tracing")]
1048            let _span = tracing::info_span!(
1049                "migration_down",
1050                version = migration_version,
1051                name = %migration.name()
1052            )
1053            .entered();
1054
1055            #[cfg(feature = "tracing")]
1056            tracing::info!("Rolling back migration");
1057
1058            // Call on_migration_start hook
1059            if let Some(ref callback) = self.on_migration_start {
1060                callback(migration_version, &migration.name());
1061            }
1062
1063            let migration_start = Instant::now();
1064
1065            // Start a transaction for this migration rollback
1066            let tx = conn.transaction()?;
1067            let migration_result = migration.down(&tx);
1068
1069            match migration_result {
1070                Ok(_) => {
1071                    // Commit the transaction
1072                    tx.commit()?;
1073
1074                    let migration_duration = migration_start.elapsed();
1075
1076                    #[cfg(feature = "tracing")]
1077                    tracing::info!(
1078                        duration_ms = migration_duration.as_millis(),
1079                        "Migration rolled back successfully"
1080                    );
1081
1082                    // Delete this migration from the tracking table
1083                    conn.execute(
1084                        &format!(
1085                            "DELETE FROM {} WHERE version = ?1",
1086                            self.schema_version_table_name
1087                        ),
1088                        params![migration_version],
1089                    )?;
1090
1091                    // record migration as rolled back
1092                    migrations_run.push(migration_version);
1093
1094                    // Call on_migration_complete hook
1095                    if let Some(ref callback) = self.on_migration_complete {
1096                        callback(migration_version, &migration.name(), migration_duration);
1097                    }
1098                }
1099                Err(e) => {
1100                    #[cfg(feature = "tracing")]
1101                    tracing::error!(
1102                        error = %e,
1103                        "Migration rollback failed"
1104                    );
1105
1106                    // Call on_migration_error hook
1107                    if let Some(ref callback) = self.on_migration_error {
1108                        callback(migration_version, &migration.name(), &e);
1109                    }
1110
1111                    // Transaction will be automatically rolled back when dropped
1112                    failing_migration = Some(MigrationFailure {
1113                        migration,
1114                        error: e,
1115                    });
1116                    break;
1117                }
1118            }
1119        }
1120
1121        Ok(MigrationReport {
1122            schema_version_table_existed,
1123            schema_version_table_created: false,
1124            failing_migration,
1125            migrations_run,
1126        })
1127    }
1128}
1129
1130#[cfg(test)]
1131mod tests {
1132    use super::*;
1133
1134    #[test]
1135    fn single_successful_from_clean() {
1136        use chrono::{DateTime, FixedOffset};
1137
1138        let mut conn = Connection::open_in_memory().unwrap();
1139        struct Migration1;
1140        impl Migration for Migration1 {
1141            fn version(&self) -> u32 {
1142                1
1143            }
1144            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1145                tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
1146                Ok(())
1147            }
1148        }
1149        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
1150        let report = migrator.upgrade(&mut conn).unwrap();
1151        assert_eq!(
1152            report,
1153            MigrationReport {
1154                schema_version_table_existed: false,
1155                schema_version_table_created: true,
1156                migrations_run: vec![1],
1157                failing_migration: None,
1158            }
1159        );
1160        // expect schema version table to exist and have recorded version 1
1161        let mut stmt = conn.prepare("SELECT * FROM _migratio_version_").unwrap();
1162        let rows = stmt
1163            .query_map([], |row| {
1164                let version: u32 = row.get("version").unwrap();
1165                let name: String = row.get("name").unwrap();
1166                let applied_at: String = row.get("applied_at").unwrap();
1167                Ok((version, name, applied_at))
1168            })
1169            .unwrap()
1170            .collect::<Result<Vec<_>, _>>()
1171            .unwrap();
1172        assert_eq!(rows.len(), 1);
1173        assert_eq!(rows[0].0, 1); // version
1174        assert_eq!(rows[0].1, "Migration 1"); // name (default)
1175        let date_string_raw = &rows[0].2;
1176        let date = DateTime::parse_from_rfc3339(&date_string_raw).unwrap();
1177        assert_eq!(date.timezone(), FixedOffset::east_opt(0).unwrap());
1178        // ensure that the date is within 5 seconds of now
1179        // this assumes this test will not take >5 seconds to run
1180        let now = Utc::now();
1181        let diff = now.timestamp() - date.timestamp();
1182        assert!(diff < 5);
1183    }
1184
1185    #[test]
1186    fn single_unsuccessful_from_clean() {
1187        let mut conn = Connection::open_in_memory().unwrap();
1188        // before running migration set up some data in the database to ensure it's preserved
1189        conn.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])
1190            .unwrap();
1191        conn.execute("INSERT INTO test (id) VALUES (1)", [])
1192            .unwrap();
1193        conn.execute("INSERT INTO test (id) VALUES (2)", [])
1194            .unwrap();
1195        // define a migration that will fail
1196        struct Migration1;
1197        impl Migration for Migration1 {
1198            fn version(&self) -> u32 {
1199                1
1200            }
1201            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1202                // do something that works
1203                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
1204                tx.execute("INSERT INTO test2 (id) SELECT id FROM test", [])?;
1205                tx.execute("DROP TABLE test", [])?;
1206                // but then do something that fails
1207                tx.execute("bleep blorp", [])?;
1208                Ok(())
1209            }
1210        }
1211        // run migration, expecting failure
1212        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
1213        let report = migrator.upgrade(&mut conn).unwrap();
1214        assert_eq!(
1215            report,
1216            MigrationReport {
1217                schema_version_table_existed: false,
1218                schema_version_table_created: false,
1219                migrations_run: vec![],
1220                failing_migration: Some(MigrationFailure {
1221                    migration: &(Box::new(Migration1) as Box<dyn Migration>),
1222                    error: Error::Rusqlite(rusqlite::Error::SqliteFailure(
1223                        rusqlite::ffi::Error {
1224                            code: rusqlite::ffi::ErrorCode::Unknown,
1225                            extended_code: 1
1226                        },
1227                        Some("near \"bleep\": syntax error".to_string())
1228                    ))
1229                })
1230            }
1231        );
1232        // expect sqlite database to be rolled back (transaction)
1233        // schema version table was created before the migration ran, so it persists
1234        let mut stmt = conn
1235            .prepare("SELECT name FROM sqlite_master WHERE type='table'")
1236            .unwrap();
1237        let mut tables = stmt
1238            .query_map([], |x| {
1239                let name: String = x.get(0)?;
1240                Ok(name)
1241            })
1242            .unwrap()
1243            .collect::<Result<Vec<String>, rusqlite::Error>>()
1244            .unwrap();
1245        tables.sort();
1246        assert_eq!(
1247            tables,
1248            vec!["_migratio_version_".to_string(), "test".to_string()]
1249        );
1250        // expect data to be unchanged
1251        let mut stmt = conn.prepare("SELECT * FROM test").unwrap();
1252        let rows = stmt
1253            .query_map([], |x| {
1254                let x: i64 = x.get(0)?;
1255                Ok(x)
1256            })
1257            .unwrap()
1258            .collect::<Result<Vec<i64>, rusqlite::Error>>()
1259            .unwrap();
1260        assert_eq!(rows, vec![1, 2]);
1261    }
1262
1263    #[test]
1264    fn upgrade_to_specific_version() {
1265        let mut conn = Connection::open_in_memory().unwrap();
1266
1267        struct Migration1;
1268        impl Migration for Migration1 {
1269            fn version(&self) -> u32 {
1270                1
1271            }
1272            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1273                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
1274                Ok(())
1275            }
1276        }
1277
1278        struct Migration2;
1279        impl Migration for Migration2 {
1280            fn version(&self) -> u32 {
1281                2
1282            }
1283            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1284                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
1285                Ok(())
1286            }
1287        }
1288
1289        struct Migration3;
1290        impl Migration for Migration3 {
1291            fn version(&self) -> u32 {
1292                3
1293            }
1294            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1295                tx.execute("CREATE TABLE test3 (id INTEGER PRIMARY KEY)", [])?;
1296                Ok(())
1297            }
1298        }
1299
1300        let migrator = SqliteMigrator::new(vec![
1301            Box::new(Migration1),
1302            Box::new(Migration2),
1303            Box::new(Migration3),
1304        ]);
1305
1306        // Upgrade to version 2 only
1307        let report = migrator.upgrade_to(&mut conn, 2).unwrap();
1308        assert_eq!(report.migrations_run, vec![1, 2]);
1309
1310        // Verify we're at version 2
1311        let version = migrator.get_current_version(&mut conn).unwrap();
1312        assert_eq!(version, 2);
1313
1314        // Verify only tables for migrations 1 and 2 exist
1315        let table_count: i32 = conn
1316            .query_row(
1317                "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name IN ('test1', 'test2', 'test3')",
1318                [],
1319                |row| row.get(0),
1320            )
1321            .unwrap();
1322        assert_eq!(table_count, 2); // test1 and test2, but not test3
1323
1324        // Now upgrade to version 3
1325        let report = migrator.upgrade_to(&mut conn, 3).unwrap();
1326        assert_eq!(report.migrations_run, vec![3]);
1327
1328        let version = migrator.get_current_version(&mut conn).unwrap();
1329        assert_eq!(version, 3);
1330    }
1331
1332    #[test]
1333    fn upgrade_to_nonexistent_version() {
1334        let mut conn = Connection::open_in_memory().unwrap();
1335
1336        struct Migration1;
1337        impl Migration for Migration1 {
1338            fn version(&self) -> u32 {
1339                1
1340            }
1341            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1342                tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
1343                Ok(())
1344            }
1345        }
1346
1347        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
1348
1349        let result = migrator.upgrade_to(&mut conn, 5);
1350        assert!(result.is_err());
1351        let err_msg = format!("{:?}", result.unwrap_err());
1352        assert!(err_msg.contains("Target version 5 does not exist"));
1353    }
1354
1355    #[test]
1356    fn success_then_failure_from_clean() {
1357        let mut conn = Connection::open_in_memory().unwrap();
1358        // before running migration set up some data in the database to ensure it's preserved
1359        conn.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])
1360            .unwrap();
1361        conn.execute("INSERT INTO test (id) VALUES (1)", [])
1362            .unwrap();
1363        conn.execute("INSERT INTO test (id) VALUES (2)", [])
1364            .unwrap();
1365        // define a migration that will succeed
1366        struct Migration1;
1367        impl Migration for Migration1 {
1368            fn version(&self) -> u32 {
1369                1
1370            }
1371            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1372                // this will succeed
1373                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
1374                // move all data from test to test2
1375                tx.execute("INSERT INTO test2 (id) SELECT id FROM test", [])?;
1376                // drop test
1377                tx.execute("DROP TABLE test", [])?;
1378                Ok(())
1379            }
1380        }
1381        // define a migration that will fail
1382        struct Migration2;
1383        impl Migration for Migration2 {
1384            fn version(&self) -> u32 {
1385                2
1386            }
1387            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1388                // do something that works
1389                tx.execute("CREATE TABLE test3 (id INTEGER PRIMARY KEY)", [])?;
1390                tx.execute("INSERT INTO test3 (id) SELECT id FROM test2", [])?;
1391                tx.execute("DROP TABLE test2", [])?;
1392                // then do something that fails
1393                tx.execute("bleep blorp", [])?;
1394                Ok(())
1395            }
1396        }
1397        // run migration, expecting failure
1398        let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
1399        let report = migrator.upgrade(&mut conn).unwrap();
1400        assert_eq!(
1401            report,
1402            MigrationReport {
1403                schema_version_table_existed: false,
1404                schema_version_table_created: true,
1405                migrations_run: vec![1],
1406                failing_migration: Some(MigrationFailure {
1407                    migration: &(Box::new(Migration2) as Box<dyn Migration>),
1408                    error: Error::Rusqlite(rusqlite::Error::SqliteFailure(
1409                        rusqlite::ffi::Error {
1410                            code: rusqlite::ffi::ErrorCode::Unknown,
1411                            extended_code: 1
1412                        },
1413                        Some("near \"bleep\": syntax error".to_string())
1414                    ))
1415                })
1416            }
1417        );
1418        // expect sqlite database to be left after migration 1
1419        // expect table names to be as expected
1420        let mut stmt = conn
1421            .prepare("SELECT name FROM sqlite_master WHERE type='table'")
1422            .unwrap();
1423        let mut tables = stmt
1424            .query_map([], |x| {
1425                let name: String = x.get(0)?;
1426                Ok(name)
1427            })
1428            .unwrap()
1429            .collect::<Result<Vec<String>, rusqlite::Error>>()
1430            .unwrap();
1431        tables.sort();
1432        assert_eq!(
1433            tables,
1434            vec!["_migratio_version_".to_string(), "test2".to_string()]
1435        );
1436        // expect data to be as expected
1437        let mut stmt = conn.prepare("SELECT * FROM test2").unwrap();
1438        let rows = stmt
1439            .query_map([], |x| {
1440                let x: i64 = x.get(0)?;
1441                Ok(x)
1442            })
1443            .unwrap()
1444            .collect::<Result<Vec<i64>, rusqlite::Error>>()
1445            .unwrap();
1446        assert_eq!(rows, vec![1, 2]);
1447    }
1448
1449    #[test]
1450    fn panic_in_migration_verify_state() {
1451        let mut conn = Connection::open_in_memory().unwrap();
1452        // before running migration set up some data in the database
1453        conn.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])
1454            .unwrap();
1455        conn.execute("INSERT INTO test (id) VALUES (1)", [])
1456            .unwrap();
1457        conn.execute("INSERT INTO test (id) VALUES (2)", [])
1458            .unwrap();
1459        // define a migration that will succeed
1460        struct Migration1;
1461        impl Migration for Migration1 {
1462            fn version(&self) -> u32 {
1463                1
1464            }
1465            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1466                // this will succeed
1467                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
1468                tx.execute("INSERT INTO test2 (id) SELECT id FROM test", [])?;
1469                tx.execute("DROP TABLE test", [])?;
1470                Ok(())
1471            }
1472        }
1473        // define a migration that will fail
1474        struct Migration2;
1475        impl Migration for Migration2 {
1476            fn version(&self) -> u32 {
1477                2
1478            }
1479            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1480                // do something that works
1481                tx.execute("CREATE TABLE test3 (id INTEGER PRIMARY KEY)", [])?;
1482                tx.execute("INSERT INTO test3 (id) SELECT id FROM test2", [])?;
1483                tx.execute("DROP TABLE test2", [])?;
1484                // then do something that fails
1485                tx.execute("bleep blorp", [])?;
1486                Ok(())
1487            }
1488        }
1489        // run migrations, expecting failure
1490        let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
1491        let report = migrator.upgrade(&mut conn).unwrap();
1492        assert_eq!(
1493            report,
1494            MigrationReport {
1495                schema_version_table_existed: false,
1496                schema_version_table_created: true,
1497                migrations_run: vec![1],
1498                failing_migration: Some(MigrationFailure {
1499                    migration: &(Box::new(Migration2) as Box<dyn Migration>),
1500                    error: Error::Rusqlite(rusqlite::Error::SqliteFailure(
1501                        rusqlite::ffi::Error {
1502                            code: rusqlite::ffi::ErrorCode::Unknown,
1503                            extended_code: 1
1504                        },
1505                        Some("near \"bleep\": syntax error".to_string())
1506                    ))
1507                })
1508            }
1509        );
1510        // With NoBackup AND transactions:
1511        // Migration1 succeeded and was committed, so those changes remain
1512        // Migration2 failed and was rolled back by the transaction
1513        // So the database should be left after Migration1 (test2 exists, test is gone)
1514        let mut stmt = conn
1515            .prepare("SELECT name FROM sqlite_master WHERE type='table'")
1516            .unwrap();
1517        let mut tables = stmt
1518            .query_map([], |x| {
1519                let name: String = x.get(0)?;
1520                Ok(name)
1521            })
1522            .unwrap()
1523            .collect::<Result<Vec<String>, rusqlite::Error>>()
1524            .unwrap();
1525        tables.sort();
1526        assert_eq!(
1527            tables,
1528            vec!["_migratio_version_".to_string(), "test2".to_string()]
1529        );
1530        // expect data to still be in test2 (from Migration1)
1531        let mut stmt = conn.prepare("SELECT * FROM test2").unwrap();
1532        let rows = stmt
1533            .query_map([], |x| {
1534                let x: i64 = x.get(0)?;
1535                Ok(x)
1536            })
1537            .unwrap()
1538            .collect::<Result<Vec<i64>, rusqlite::Error>>()
1539            .unwrap();
1540        assert_eq!(rows, vec![1, 2]);
1541    }
1542
1543    #[test]
1544    fn panic_with_successful_prior_migration() {
1545        use std::panic;
1546
1547        let mut conn = Connection::open_in_memory().unwrap();
1548        // Set up initial data
1549        conn.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])
1550            .unwrap();
1551        conn.execute("INSERT INTO test (id) VALUES (1)", [])
1552            .unwrap();
1553        conn.execute("INSERT INTO test (id) VALUES (2)", [])
1554            .unwrap();
1555
1556        // Define a migration that succeeds
1557        struct Migration1;
1558        impl Migration for Migration1 {
1559            fn version(&self) -> u32 {
1560                1
1561            }
1562            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1563                // This migration succeeds
1564                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
1565                tx.execute("INSERT INTO test2 (id) SELECT id FROM test", [])?;
1566                tx.execute("DROP TABLE test", [])?;
1567                Ok(())
1568            }
1569        }
1570
1571        // Define a migration that panics
1572        struct Migration2;
1573        impl Migration for Migration2 {
1574            fn version(&self) -> u32 {
1575                2
1576            }
1577            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1578                // Make some changes
1579                tx.execute("CREATE TABLE test3 (id INTEGER PRIMARY KEY)", [])?;
1580                tx.execute("INSERT INTO test3 (id) SELECT id FROM test2", [])?;
1581                tx.execute("DROP TABLE test2", [])?;
1582                // Then panic
1583                panic!("Migration panic!");
1584            }
1585        }
1586
1587        // Run migrations - catch the panic
1588        let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
1589        let result = panic::catch_unwind(panic::AssertUnwindSafe(|| migrator.upgrade(&mut conn)));
1590
1591        // Verify panic occurred
1592        assert!(result.is_err());
1593
1594        // When a panic occurs during Migration2:
1595        // Migration1 completed successfully and was committed
1596        // Migration2's transaction is automatically rolled back when the panic unwinds
1597        // The database should be in the state after Migration1 (test2 exists, test is gone)
1598        let mut stmt = conn
1599            .prepare("SELECT name FROM sqlite_master WHERE type='table'")
1600            .unwrap();
1601        let mut tables = stmt
1602            .query_map([], |x| {
1603                let name: String = x.get(0)?;
1604                Ok(name)
1605            })
1606            .unwrap()
1607            .collect::<Result<Vec<String>, rusqlite::Error>>()
1608            .unwrap();
1609        tables.sort();
1610
1611        // Assert current behavior: Migration1 succeeded, Migration2 rolled back
1612        assert_eq!(
1613            tables,
1614            vec!["_migratio_version_".to_string(), "test2".to_string()]
1615        );
1616
1617        // Verify data from Migration1 is intact
1618        let mut stmt = conn.prepare("SELECT * FROM test2").unwrap();
1619        let rows = stmt
1620            .query_map([], |x| {
1621                let x: i64 = x.get(0)?;
1622                Ok(x)
1623            })
1624            .unwrap()
1625            .collect::<Result<Vec<i64>, rusqlite::Error>>()
1626            .unwrap();
1627        assert_eq!(rows, vec![1, 2]);
1628
1629        // Verify schema_version table has version 1 (Migration1 completed, Migration2 never completed)
1630        let mut stmt = conn
1631            .prepare("SELECT version FROM _migratio_version_")
1632            .unwrap();
1633        let version: u32 = stmt.query_row([], |row| row.get(0)).unwrap();
1634        assert_eq!(version, 1);
1635    }
1636
1637    #[test]
1638    fn incremental_migrations_different_applied_at() {
1639        use std::thread;
1640        use std::time::Duration;
1641
1642        let mut conn = Connection::open_in_memory().unwrap();
1643
1644        // Define first set of migrations
1645        struct Migration1;
1646        impl Migration for Migration1 {
1647            fn version(&self) -> u32 {
1648                1
1649            }
1650            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1651                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
1652                Ok(())
1653            }
1654            fn name(&self) -> String {
1655                "create_test1_table".to_string()
1656            }
1657        }
1658
1659        struct Migration2;
1660        impl Migration for Migration2 {
1661            fn version(&self) -> u32 {
1662                2
1663            }
1664            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1665                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
1666                Ok(())
1667            }
1668            fn name(&self) -> String {
1669                "create_test2_table".to_string()
1670            }
1671        }
1672
1673        // Run first batch of migrations
1674        let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
1675        let report = migrator.upgrade(&mut conn).unwrap();
1676        assert_eq!(report.migrations_run, vec![1, 2]);
1677
1678        // Get the applied_at timestamp for first batch
1679        let first_batch: Vec<(u32, String, String)> = {
1680            let mut stmt = conn
1681                .prepare(
1682                    "SELECT version, name, applied_at FROM _migratio_version_ ORDER BY version",
1683                )
1684                .unwrap();
1685            stmt.query_map([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))
1686                .unwrap()
1687                .collect::<Result<Vec<_>, _>>()
1688                .unwrap()
1689        };
1690
1691        assert_eq!(first_batch.len(), 2);
1692        assert_eq!(first_batch[0].0, 1);
1693        assert_eq!(first_batch[0].1, "create_test1_table");
1694        assert_eq!(first_batch[1].0, 2);
1695        assert_eq!(first_batch[1].1, "create_test2_table");
1696        // Both migrations in first batch should have same timestamp
1697        assert_eq!(first_batch[0].2, first_batch[1].2);
1698        let first_batch_timestamp = first_batch[0].2.clone();
1699
1700        // Wait a bit to ensure different timestamp
1701        thread::sleep(Duration::from_millis(2));
1702
1703        // Define third migration
1704        struct Migration3;
1705        impl Migration for Migration3 {
1706            fn version(&self) -> u32 {
1707                3
1708            }
1709            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1710                tx.execute("CREATE TABLE test3 (id INTEGER PRIMARY KEY)", [])?;
1711                Ok(())
1712            }
1713            fn name(&self) -> String {
1714                "create_test3_table".to_string()
1715            }
1716        }
1717
1718        // Run second batch with all three migrations (only migration 3 should run)
1719        let migrator = SqliteMigrator::new(vec![
1720            Box::new(Migration1),
1721            Box::new(Migration2),
1722            Box::new(Migration3),
1723        ]);
1724        let report = migrator.upgrade(&mut conn).unwrap();
1725        assert_eq!(report.migrations_run, vec![3]);
1726
1727        // Verify all three migrations are recorded
1728        let all_migrations: Vec<(u32, String, String)> = {
1729            let mut stmt = conn
1730                .prepare(
1731                    "SELECT version, name, applied_at FROM _migratio_version_ ORDER BY version",
1732                )
1733                .unwrap();
1734            stmt.query_map([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))
1735                .unwrap()
1736                .collect::<Result<Vec<_>, _>>()
1737                .unwrap()
1738        };
1739
1740        assert_eq!(all_migrations.len(), 3);
1741        assert_eq!(all_migrations[0].0, 1);
1742        assert_eq!(all_migrations[1].0, 2);
1743        assert_eq!(all_migrations[2].0, 3);
1744        assert_eq!(all_migrations[2].1, "create_test3_table");
1745
1746        // First two should have same timestamp (from first batch)
1747        assert_eq!(all_migrations[0].2, all_migrations[1].2);
1748        // Third should have different timestamp (from second batch)
1749        assert_ne!(all_migrations[2].2, first_batch_timestamp);
1750    }
1751
1752    #[test]
1753    #[should_panic(expected = "Migration version must be greater than 0")]
1754    fn new_rejects_zero_version() {
1755        struct Migration0;
1756        impl Migration for Migration0 {
1757            fn version(&self) -> u32 {
1758                0
1759            }
1760            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
1761                Ok(())
1762            }
1763        }
1764
1765        let _ = SqliteMigrator::new(vec![Box::new(Migration0)]);
1766    }
1767
1768    #[test]
1769    #[should_panic(expected = "Duplicate migration version found: 2")]
1770    fn new_rejects_duplicate_versions() {
1771        struct Migration1;
1772        impl Migration for Migration1 {
1773            fn version(&self) -> u32 {
1774                1
1775            }
1776            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
1777                Ok(())
1778            }
1779        }
1780
1781        struct Migration2a;
1782        impl Migration for Migration2a {
1783            fn version(&self) -> u32 {
1784                2
1785            }
1786            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
1787                Ok(())
1788            }
1789        }
1790
1791        struct Migration2b;
1792        impl Migration for Migration2b {
1793            fn version(&self) -> u32 {
1794                2
1795            }
1796            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
1797                Ok(())
1798            }
1799        }
1800
1801        let _ = SqliteMigrator::new(vec![
1802            Box::new(Migration1),
1803            Box::new(Migration2a),
1804            Box::new(Migration2b),
1805        ]);
1806    }
1807
1808    #[test]
1809    #[should_panic(expected = "Migration versions must start at 1, found starting version: 2")]
1810    fn new_rejects_non_starting_at_one() {
1811        struct Migration2;
1812        impl Migration for Migration2 {
1813            fn version(&self) -> u32 {
1814                2
1815            }
1816            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
1817                Ok(())
1818            }
1819        }
1820
1821        struct Migration3;
1822        impl Migration for Migration3 {
1823            fn version(&self) -> u32 {
1824                3
1825            }
1826            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
1827                Ok(())
1828            }
1829        }
1830
1831        let _ = SqliteMigrator::new(vec![Box::new(Migration2), Box::new(Migration3)]);
1832    }
1833
1834    #[test]
1835    #[should_panic(expected = "Migration versions must be contiguous. Expected version 2, found 3")]
1836    fn new_rejects_non_contiguous() {
1837        struct Migration1;
1838        impl Migration for Migration1 {
1839            fn version(&self) -> u32 {
1840                1
1841            }
1842            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
1843                Ok(())
1844            }
1845        }
1846
1847        struct Migration3;
1848        impl Migration for Migration3 {
1849            fn version(&self) -> u32 {
1850                3
1851            }
1852            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
1853                Ok(())
1854            }
1855        }
1856
1857        let _ = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration3)]);
1858    }
1859
1860    #[test]
1861    fn try_new_returns_err_for_non_starting_at_one() {
1862        struct Migration2;
1863        impl Migration for Migration2 {
1864            fn version(&self) -> u32 {
1865                2
1866            }
1867            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
1868                Ok(())
1869            }
1870        }
1871
1872        let result = SqliteMigrator::try_new(vec![Box::new(Migration2)]);
1873        assert!(result.is_err());
1874        assert_eq!(
1875            result.unwrap_err(),
1876            "Migration versions must start at 1, found starting version: 2"
1877        );
1878    }
1879
1880    #[test]
1881    fn try_new_returns_err_for_duplicate_versions() {
1882        struct Migration1;
1883        impl Migration for Migration1 {
1884            fn version(&self) -> u32 {
1885                1
1886            }
1887            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
1888                Ok(())
1889            }
1890        }
1891
1892        struct Migration2a;
1893        impl Migration for Migration2a {
1894            fn version(&self) -> u32 {
1895                2
1896            }
1897            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
1898                Ok(())
1899            }
1900        }
1901
1902        struct Migration2b;
1903        impl Migration for Migration2b {
1904            fn version(&self) -> u32 {
1905                2
1906            }
1907            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
1908                Ok(())
1909            }
1910        }
1911
1912        let result = SqliteMigrator::try_new(vec![
1913            Box::new(Migration1),
1914            Box::new(Migration2a),
1915            Box::new(Migration2b),
1916        ]);
1917        assert!(result.is_err());
1918        assert_eq!(result.unwrap_err(), "Duplicate migration version found: 2");
1919    }
1920
1921    #[test]
1922    fn try_new_returns_ok_for_valid_migrations() {
1923        struct Migration1;
1924        impl Migration for Migration1 {
1925            fn version(&self) -> u32 {
1926                1
1927            }
1928            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
1929                Ok(())
1930            }
1931        }
1932
1933        struct Migration2;
1934        impl Migration for Migration2 {
1935            fn version(&self) -> u32 {
1936                2
1937            }
1938            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
1939                Ok(())
1940            }
1941        }
1942
1943        let result = SqliteMigrator::try_new(vec![Box::new(Migration1), Box::new(Migration2)]);
1944        assert!(result.is_ok());
1945    }
1946
1947    #[test]
1948    fn checksum_validation_detects_modified_migration() {
1949        let mut conn = Connection::open_in_memory().unwrap();
1950
1951        // Define initial migration
1952        struct Migration1V1;
1953        impl Migration for Migration1V1 {
1954            fn version(&self) -> u32 {
1955                1
1956            }
1957            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1958                tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
1959                Ok(())
1960            }
1961            fn name(&self) -> String {
1962                "create_test_table".to_string()
1963            }
1964        }
1965
1966        // Run first migration
1967        let migrator = SqliteMigrator::new(vec![Box::new(Migration1V1)]);
1968        let report = migrator.upgrade(&mut conn).unwrap();
1969        assert_eq!(report.migrations_run, vec![1]);
1970
1971        // Now define the same migration but with a different name (simulating modification)
1972        struct Migration1V2;
1973        impl Migration for Migration1V2 {
1974            fn version(&self) -> u32 {
1975                1
1976            }
1977            fn up(&self, tx: &Transaction) -> Result<(), Error> {
1978                tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
1979                Ok(())
1980            }
1981            fn name(&self) -> String {
1982                "create_test_table_modified".to_string() // Different name!
1983            }
1984        }
1985
1986        // Try to upgrade with modified migration - should fail
1987        let migrator = SqliteMigrator::new(vec![Box::new(Migration1V2)]);
1988        let result = migrator.upgrade(&mut conn);
1989
1990        assert!(result.is_err());
1991        let err_msg = format!("{:?}", result.unwrap_err());
1992        assert!(err_msg.contains("checksum mismatch"));
1993        assert!(err_msg.contains("Migration 1"));
1994    }
1995
1996    #[test]
1997    fn checksum_validation_passes_for_unmodified_migrations() {
1998        let mut conn = Connection::open_in_memory().unwrap();
1999
2000        // Define migrations
2001        struct Migration1;
2002        impl Migration for Migration1 {
2003            fn version(&self) -> u32 {
2004                1
2005            }
2006            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2007                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
2008                Ok(())
2009            }
2010            fn name(&self) -> String {
2011                "create_test1_table".to_string()
2012            }
2013        }
2014
2015        struct Migration2;
2016        impl Migration for Migration2 {
2017            fn version(&self) -> u32 {
2018                2
2019            }
2020            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2021                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
2022                Ok(())
2023            }
2024            fn name(&self) -> String {
2025                "create_test2_table".to_string()
2026            }
2027        }
2028
2029        // Run first migration
2030        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
2031        let report = migrator.upgrade(&mut conn).unwrap();
2032        assert_eq!(report.migrations_run, vec![1]);
2033
2034        // Run both migrations (only second should execute)
2035        let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
2036        let report = migrator.upgrade(&mut conn).unwrap();
2037        assert_eq!(report.migrations_run, vec![2]);
2038
2039        // Run again - should succeed with no migrations run (validates checksums are still correct)
2040        let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
2041        let report = migrator.upgrade(&mut conn).unwrap();
2042        assert_eq!(report.migrations_run, vec![] as Vec<u32>);
2043    }
2044
2045    #[test]
2046    fn checksums_stored_in_database() {
2047        let mut conn = Connection::open_in_memory().unwrap();
2048
2049        struct Migration1;
2050        impl Migration for Migration1 {
2051            fn version(&self) -> u32 {
2052                1
2053            }
2054            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2055                tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
2056                Ok(())
2057            }
2058            fn name(&self) -> String {
2059                "my_migration".to_string()
2060            }
2061        }
2062
2063        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
2064        migrator.upgrade(&mut conn).unwrap();
2065
2066        // Verify checksum was stored
2067        let mut stmt = conn
2068            .prepare("SELECT checksum FROM _migratio_version_ WHERE version = 1")
2069            .unwrap();
2070        let checksum: String = stmt.query_row([], |row| row.get(0)).unwrap();
2071
2072        // Checksum should be a non-empty hex string (SHA-256 = 64 chars)
2073        assert_eq!(checksum.len(), 64);
2074        assert!(checksum.chars().all(|c| c.is_ascii_hexdigit()));
2075
2076        // Verify it matches the calculated checksum
2077        let migration = Box::new(Migration1) as Box<dyn Migration>;
2078        let expected_checksum = SqliteMigrator::calculate_checksum(&migration);
2079        assert_eq!(checksum, expected_checksum);
2080    }
2081
2082    #[test]
2083    fn downgrade_single_migration() {
2084        let mut conn = Connection::open_in_memory().unwrap();
2085
2086        struct Migration1;
2087        impl Migration for Migration1 {
2088            fn version(&self) -> u32 {
2089                1
2090            }
2091            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2092                tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
2093                Ok(())
2094            }
2095            fn down(&self, tx: &Transaction) -> Result<(), Error> {
2096                tx.execute("DROP TABLE test", [])?;
2097                Ok(())
2098            }
2099        }
2100
2101        // Apply migration
2102        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
2103        let report = migrator.upgrade(&mut conn).unwrap();
2104        assert_eq!(report.migrations_run, vec![1]);
2105
2106        // Verify table exists
2107        {
2108            let mut stmt = conn
2109                .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='test'")
2110                .unwrap();
2111            assert!(stmt.query([]).unwrap().next().unwrap().is_some());
2112        }
2113
2114        // Rollback migration
2115        let report = migrator.downgrade(&mut conn, 0).unwrap();
2116        assert_eq!(report.migrations_run, vec![1]);
2117
2118        // Verify table is gone
2119        {
2120            let mut stmt = conn
2121                .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='test'")
2122                .unwrap();
2123            assert!(stmt.query([]).unwrap().next().unwrap().is_none());
2124        }
2125
2126        // Verify tracking table shows no migrations
2127        let mut stmt = conn
2128            .prepare("SELECT COUNT(*) FROM _migratio_version_")
2129            .unwrap();
2130        let count: i64 = stmt.query_row([], |row| row.get(0)).unwrap();
2131        assert_eq!(count, 0);
2132    }
2133
2134    #[test]
2135    fn downgrade_multiple_migrations() {
2136        let mut conn = Connection::open_in_memory().unwrap();
2137
2138        struct Migration1;
2139        impl Migration for Migration1 {
2140            fn version(&self) -> u32 {
2141                1
2142            }
2143            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2144                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
2145                Ok(())
2146            }
2147            fn down(&self, tx: &Transaction) -> Result<(), Error> {
2148                tx.execute("DROP TABLE test1", [])?;
2149                Ok(())
2150            }
2151        }
2152
2153        struct Migration2;
2154        impl Migration for Migration2 {
2155            fn version(&self) -> u32 {
2156                2
2157            }
2158            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2159                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
2160                Ok(())
2161            }
2162            fn down(&self, tx: &Transaction) -> Result<(), Error> {
2163                tx.execute("DROP TABLE test2", [])?;
2164                Ok(())
2165            }
2166        }
2167
2168        struct Migration3;
2169        impl Migration for Migration3 {
2170            fn version(&self) -> u32 {
2171                3
2172            }
2173            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2174                tx.execute("CREATE TABLE test3 (id INTEGER PRIMARY KEY)", [])?;
2175                Ok(())
2176            }
2177            fn down(&self, tx: &Transaction) -> Result<(), Error> {
2178                tx.execute("DROP TABLE test3", [])?;
2179                Ok(())
2180            }
2181        }
2182
2183        // Apply all migrations
2184        let migrator = SqliteMigrator::new(vec![
2185            Box::new(Migration1),
2186            Box::new(Migration2),
2187            Box::new(Migration3),
2188        ]);
2189        let report = migrator.upgrade(&mut conn).unwrap();
2190        assert_eq!(report.migrations_run, vec![1, 2, 3]);
2191
2192        // Rollback to version 1 (should rollback migrations 3 and 2, in that order)
2193        let report = migrator.downgrade(&mut conn, 1).unwrap();
2194        assert_eq!(report.migrations_run, vec![3, 2]);
2195
2196        // Verify only test1 table exists
2197        let mut stmt = conn
2198            .prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
2199            .unwrap();
2200        let tables: Vec<String> = stmt
2201            .query_map([], |row| row.get(0))
2202            .unwrap()
2203            .collect::<Result<Vec<_>, _>>()
2204            .unwrap();
2205        assert_eq!(tables, vec!["_migratio_version_", "test1"]);
2206
2207        // Verify tracking table shows only migration 1
2208        let mut stmt = conn
2209            .prepare("SELECT version FROM _migratio_version_ ORDER BY version")
2210            .unwrap();
2211        let versions: Vec<u32> = stmt
2212            .query_map([], |row| row.get(0))
2213            .unwrap()
2214            .collect::<Result<Vec<_>, _>>()
2215            .unwrap();
2216        assert_eq!(versions, vec![1]);
2217    }
2218
2219    #[test]
2220    fn downgrade_all_migrations() {
2221        let mut conn = Connection::open_in_memory().unwrap();
2222
2223        struct Migration1;
2224        impl Migration for Migration1 {
2225            fn version(&self) -> u32 {
2226                1
2227            }
2228            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2229                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
2230                Ok(())
2231            }
2232            fn down(&self, tx: &Transaction) -> Result<(), Error> {
2233                tx.execute("DROP TABLE test1", [])?;
2234                Ok(())
2235            }
2236        }
2237
2238        struct Migration2;
2239        impl Migration for Migration2 {
2240            fn version(&self) -> u32 {
2241                2
2242            }
2243            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2244                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
2245                Ok(())
2246            }
2247            fn down(&self, tx: &Transaction) -> Result<(), Error> {
2248                tx.execute("DROP TABLE test2", [])?;
2249                Ok(())
2250            }
2251        }
2252
2253        // Apply migrations
2254        let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
2255        let report = migrator.upgrade(&mut conn).unwrap();
2256        assert_eq!(report.migrations_run, vec![1, 2]);
2257
2258        // Rollback all migrations (target_version = 0)
2259        let report = migrator.downgrade(&mut conn, 0).unwrap();
2260        assert_eq!(report.migrations_run, vec![2, 1]);
2261
2262        // Verify no tables except tracking table
2263        let mut stmt = conn
2264            .prepare("SELECT name FROM sqlite_master WHERE type='table'")
2265            .unwrap();
2266        let tables: Vec<String> = stmt
2267            .query_map([], |row| row.get(0))
2268            .unwrap()
2269            .collect::<Result<Vec<_>, _>>()
2270            .unwrap();
2271        assert_eq!(tables, vec!["_migratio_version_"]);
2272
2273        // Verify tracking table is empty
2274        let mut stmt = conn
2275            .prepare("SELECT COUNT(*) FROM _migratio_version_")
2276            .unwrap();
2277        let count: i64 = stmt.query_row([], |row| row.get(0)).unwrap();
2278        assert_eq!(count, 0);
2279    }
2280
2281    #[test]
2282    fn downgrade_on_clean_database() {
2283        let mut conn = Connection::open_in_memory().unwrap();
2284
2285        struct Migration1;
2286        impl Migration for Migration1 {
2287            fn version(&self) -> u32 {
2288                1
2289            }
2290            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
2291                Ok(())
2292            }
2293            fn down(&self, _tx: &Transaction) -> Result<(), Error> {
2294                Ok(())
2295            }
2296        }
2297
2298        // Downgrade on clean database should succeed with no migrations run
2299        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
2300        let report = migrator.downgrade(&mut conn, 0).unwrap();
2301        assert_eq!(report.migrations_run, vec![] as Vec<u32>);
2302        assert!(!report.schema_version_table_existed);
2303    }
2304
2305    #[test]
2306    fn downgrade_with_invalid_target_version() {
2307        let mut conn = Connection::open_in_memory().unwrap();
2308
2309        struct Migration1;
2310        impl Migration for Migration1 {
2311            fn version(&self) -> u32 {
2312                1
2313            }
2314            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2315                tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
2316                Ok(())
2317            }
2318            fn down(&self, tx: &Transaction) -> Result<(), Error> {
2319                tx.execute("DROP TABLE test", [])?;
2320                Ok(())
2321            }
2322        }
2323
2324        // Apply migration
2325        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
2326        migrator.upgrade(&mut conn).unwrap();
2327
2328        // Try to downgrade to a version higher than current (should fail)
2329        let result = migrator.downgrade(&mut conn, 5);
2330        assert!(result.is_err());
2331        let err_msg = format!("{:?}", result.unwrap_err());
2332        assert!(err_msg.contains("Cannot downgrade to version 5 when current version is 1"));
2333    }
2334
2335    #[test]
2336    #[should_panic(expected = "does not support downgrade")]
2337    fn downgrade_panics_when_down_not_implemented() {
2338        let mut conn = Connection::open_in_memory().unwrap();
2339
2340        struct Migration1;
2341        impl Migration for Migration1 {
2342            fn version(&self) -> u32 {
2343                1
2344            }
2345            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2346                tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
2347                Ok(())
2348            }
2349            // No down() implementation - uses default that panics
2350        }
2351
2352        // Apply migration
2353        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
2354        migrator.upgrade(&mut conn).unwrap();
2355
2356        // Try to downgrade - should panic
2357        let _ = migrator.downgrade(&mut conn, 0);
2358    }
2359
2360    #[test]
2361    fn downgrade_validates_checksums() {
2362        let mut conn = Connection::open_in_memory().unwrap();
2363
2364        struct Migration1V1;
2365        impl Migration for Migration1V1 {
2366            fn version(&self) -> u32 {
2367                1
2368            }
2369            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2370                tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
2371                Ok(())
2372            }
2373            fn down(&self, tx: &Transaction) -> Result<(), Error> {
2374                tx.execute("DROP TABLE test", [])?;
2375                Ok(())
2376            }
2377            fn name(&self) -> String {
2378                "create_test_table".to_string()
2379            }
2380        }
2381
2382        // Apply migration
2383        let migrator = SqliteMigrator::new(vec![Box::new(Migration1V1)]);
2384        migrator.upgrade(&mut conn).unwrap();
2385
2386        // Try to downgrade with modified migration
2387        struct Migration1V2;
2388        impl Migration for Migration1V2 {
2389            fn version(&self) -> u32 {
2390                1
2391            }
2392            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2393                tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
2394                Ok(())
2395            }
2396            fn down(&self, tx: &Transaction) -> Result<(), Error> {
2397                tx.execute("DROP TABLE test", [])?;
2398                Ok(())
2399            }
2400            fn name(&self) -> String {
2401                "create_test_table_modified".to_string() // Different!
2402            }
2403        }
2404
2405        let migrator = SqliteMigrator::new(vec![Box::new(Migration1V2)]);
2406        let result = migrator.downgrade(&mut conn, 0);
2407
2408        assert!(result.is_err());
2409        let err_msg = format!("{:?}", result.unwrap_err());
2410        assert!(err_msg.contains("checksum mismatch"));
2411    }
2412
2413    #[test]
2414    fn get_current_version_on_clean_database() {
2415        let mut conn = Connection::open_in_memory().unwrap();
2416
2417        struct Migration1;
2418        impl Migration for Migration1 {
2419            fn version(&self) -> u32 {
2420                1
2421            }
2422            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
2423                Ok(())
2424            }
2425        }
2426
2427        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
2428        let version = migrator.get_current_version(&mut conn).unwrap();
2429        assert_eq!(version, 0);
2430    }
2431
2432    #[test]
2433    fn get_current_version_after_migrations() {
2434        let mut conn = Connection::open_in_memory().unwrap();
2435
2436        struct Migration1;
2437        impl Migration for Migration1 {
2438            fn version(&self) -> u32 {
2439                1
2440            }
2441            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2442                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
2443                Ok(())
2444            }
2445        }
2446
2447        struct Migration2;
2448        impl Migration for Migration2 {
2449            fn version(&self) -> u32 {
2450                2
2451            }
2452            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2453                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
2454                Ok(())
2455            }
2456        }
2457
2458        let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
2459
2460        // Before any migrations
2461        assert_eq!(migrator.get_current_version(&mut conn).unwrap(), 0);
2462
2463        // After first migration
2464        migrator.upgrade(&mut conn).unwrap();
2465        assert_eq!(migrator.get_current_version(&mut conn).unwrap(), 2);
2466    }
2467
2468    #[test]
2469    fn preview_upgrade_on_clean_database() {
2470        let mut conn = Connection::open_in_memory().unwrap();
2471
2472        struct Migration1;
2473        impl Migration for Migration1 {
2474            fn version(&self) -> u32 {
2475                1
2476            }
2477            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
2478                Ok(())
2479            }
2480        }
2481
2482        struct Migration2;
2483        impl Migration for Migration2 {
2484            fn version(&self) -> u32 {
2485                2
2486            }
2487            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
2488                Ok(())
2489            }
2490        }
2491
2492        let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
2493        let pending = migrator.preview_upgrade(&mut conn).unwrap();
2494
2495        assert_eq!(pending.len(), 2);
2496        assert_eq!(pending[0].version(), 1);
2497        assert_eq!(pending[1].version(), 2);
2498    }
2499
2500    #[test]
2501    fn preview_upgrade_with_partial_migrations() {
2502        let mut conn = Connection::open_in_memory().unwrap();
2503
2504        struct Migration1;
2505        impl Migration for Migration1 {
2506            fn version(&self) -> u32 {
2507                1
2508            }
2509            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2510                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
2511                Ok(())
2512            }
2513        }
2514
2515        struct Migration2;
2516        impl Migration for Migration2 {
2517            fn version(&self) -> u32 {
2518                2
2519            }
2520            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2521                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
2522                Ok(())
2523            }
2524        }
2525
2526        struct Migration3;
2527        impl Migration for Migration3 {
2528            fn version(&self) -> u32 {
2529                3
2530            }
2531            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2532                tx.execute("CREATE TABLE test3 (id INTEGER PRIMARY KEY)", [])?;
2533                Ok(())
2534            }
2535        }
2536
2537        // Apply first migration
2538        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
2539        migrator.upgrade(&mut conn).unwrap();
2540
2541        // Preview with all three migrations
2542        let migrator = SqliteMigrator::new(vec![
2543            Box::new(Migration1),
2544            Box::new(Migration2),
2545            Box::new(Migration3),
2546        ]);
2547        let pending = migrator.preview_upgrade(&mut conn).unwrap();
2548
2549        assert_eq!(pending.len(), 2);
2550        assert_eq!(pending[0].version(), 2);
2551        assert_eq!(pending[1].version(), 3);
2552    }
2553
2554    #[test]
2555    fn preview_upgrade_when_up_to_date() {
2556        let mut conn = Connection::open_in_memory().unwrap();
2557
2558        struct Migration1;
2559        impl Migration for Migration1 {
2560            fn version(&self) -> u32 {
2561                1
2562            }
2563            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2564                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
2565                Ok(())
2566            }
2567        }
2568
2569        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
2570        migrator.upgrade(&mut conn).unwrap();
2571
2572        let pending = migrator.preview_upgrade(&mut conn).unwrap();
2573        assert_eq!(pending.len(), 0);
2574    }
2575
2576    #[test]
2577    fn preview_downgrade_to_zero() {
2578        let mut conn = Connection::open_in_memory().unwrap();
2579
2580        struct Migration1;
2581        impl Migration for Migration1 {
2582            fn version(&self) -> u32 {
2583                1
2584            }
2585            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2586                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
2587                Ok(())
2588            }
2589            fn down(&self, tx: &Transaction) -> Result<(), Error> {
2590                tx.execute("DROP TABLE test1", [])?;
2591                Ok(())
2592            }
2593        }
2594
2595        struct Migration2;
2596        impl Migration for Migration2 {
2597            fn version(&self) -> u32 {
2598                2
2599            }
2600            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2601                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
2602                Ok(())
2603            }
2604            fn down(&self, tx: &Transaction) -> Result<(), Error> {
2605                tx.execute("DROP TABLE test2", [])?;
2606                Ok(())
2607            }
2608        }
2609
2610        let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
2611        migrator.upgrade(&mut conn).unwrap();
2612
2613        let to_rollback = migrator.preview_downgrade(&mut conn, 0).unwrap();
2614
2615        assert_eq!(to_rollback.len(), 2);
2616        assert_eq!(to_rollback[0].version(), 2); // Reverse order
2617        assert_eq!(to_rollback[1].version(), 1);
2618    }
2619
2620    #[test]
2621    fn preview_downgrade_to_specific_version() {
2622        let mut conn = Connection::open_in_memory().unwrap();
2623
2624        struct Migration1;
2625        impl Migration for Migration1 {
2626            fn version(&self) -> u32 {
2627                1
2628            }
2629            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2630                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
2631                Ok(())
2632            }
2633            fn down(&self, tx: &Transaction) -> Result<(), Error> {
2634                tx.execute("DROP TABLE test1", [])?;
2635                Ok(())
2636            }
2637        }
2638
2639        struct Migration2;
2640        impl Migration for Migration2 {
2641            fn version(&self) -> u32 {
2642                2
2643            }
2644            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2645                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
2646                Ok(())
2647            }
2648            fn down(&self, tx: &Transaction) -> Result<(), Error> {
2649                tx.execute("DROP TABLE test2", [])?;
2650                Ok(())
2651            }
2652        }
2653
2654        struct Migration3;
2655        impl Migration for Migration3 {
2656            fn version(&self) -> u32 {
2657                3
2658            }
2659            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2660                tx.execute("CREATE TABLE test3 (id INTEGER PRIMARY KEY)", [])?;
2661                Ok(())
2662            }
2663            fn down(&self, tx: &Transaction) -> Result<(), Error> {
2664                tx.execute("DROP TABLE test3", [])?;
2665                Ok(())
2666            }
2667        }
2668
2669        let migrator = SqliteMigrator::new(vec![
2670            Box::new(Migration1),
2671            Box::new(Migration2),
2672            Box::new(Migration3),
2673        ]);
2674        migrator.upgrade(&mut conn).unwrap();
2675
2676        let to_rollback = migrator.preview_downgrade(&mut conn, 1).unwrap();
2677
2678        assert_eq!(to_rollback.len(), 2);
2679        assert_eq!(to_rollback[0].version(), 3); // Reverse order
2680        assert_eq!(to_rollback[1].version(), 2);
2681    }
2682
2683    #[test]
2684    fn preview_downgrade_on_clean_database() {
2685        let mut conn = Connection::open_in_memory().unwrap();
2686
2687        struct Migration1;
2688        impl Migration for Migration1 {
2689            fn version(&self) -> u32 {
2690                1
2691            }
2692            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
2693                Ok(())
2694            }
2695            fn down(&self, _tx: &Transaction) -> Result<(), Error> {
2696                Ok(())
2697            }
2698        }
2699
2700        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
2701        let to_rollback = migrator.preview_downgrade(&mut conn, 0).unwrap();
2702
2703        assert_eq!(to_rollback.len(), 0);
2704    }
2705
2706    #[test]
2707    fn preview_downgrade_with_invalid_target() {
2708        let mut conn = Connection::open_in_memory().unwrap();
2709
2710        struct Migration1;
2711        impl Migration for Migration1 {
2712            fn version(&self) -> u32 {
2713                1
2714            }
2715            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2716                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
2717                Ok(())
2718            }
2719            fn down(&self, _tx: &Transaction) -> Result<(), Error> {
2720                Ok(())
2721            }
2722        }
2723
2724        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
2725        migrator.upgrade(&mut conn).unwrap();
2726
2727        let result = migrator.preview_downgrade(&mut conn, 5);
2728        assert!(result.is_err());
2729        let err_msg = format!("{:?}", result.unwrap_err());
2730        assert!(err_msg.contains("Cannot downgrade to version 5 when current version is 1"));
2731    }
2732
2733    #[test]
2734    fn migration_failure_accessors() {
2735        let mut conn = Connection::open_in_memory().unwrap();
2736
2737        struct Migration1;
2738        impl Migration for Migration1 {
2739            fn version(&self) -> u32 {
2740                1
2741            }
2742            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2743                tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
2744                Ok(())
2745            }
2746            fn name(&self) -> String {
2747                "create_test_table".to_string()
2748            }
2749        }
2750
2751        struct Migration2;
2752        impl Migration for Migration2 {
2753            fn version(&self) -> u32 {
2754                2
2755            }
2756            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2757                // Intentionally fail
2758                tx.execute("INVALID SQL HERE", [])?;
2759                Ok(())
2760            }
2761            fn name(&self) -> String {
2762                "failing_migration".to_string()
2763            }
2764        }
2765
2766        let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
2767        let report = migrator.upgrade(&mut conn).unwrap();
2768
2769        // Check that we can access the failure
2770        assert!(report.failing_migration.is_some());
2771
2772        let failure = report.failing_migration.as_ref().unwrap();
2773
2774        // Test migration accessor
2775        let migration = failure.migration();
2776        assert_eq!(migration.version(), 2);
2777        assert_eq!(migration.name(), "failing_migration");
2778
2779        // Test error accessor
2780        let error = failure.error();
2781        assert!(matches!(error, Error::Rusqlite(_)));
2782    }
2783
2784    #[test]
2785    fn migration_failure_can_be_pattern_matched() {
2786        let mut conn = Connection::open_in_memory().unwrap();
2787
2788        struct Migration1;
2789        impl Migration for Migration1 {
2790            fn version(&self) -> u32 {
2791                1
2792            }
2793            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2794                tx.execute("INVALID SQL", [])?;
2795                Ok(())
2796            }
2797        }
2798
2799        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
2800        let report = migrator.upgrade(&mut conn).unwrap();
2801
2802        // Demonstrate pattern matching on the failure
2803        match report.failing_migration {
2804            Some(ref failure) => {
2805                println!(
2806                    "Migration {} failed: {:?}",
2807                    failure.migration().version(),
2808                    failure.error()
2809                );
2810                assert_eq!(failure.migration().version(), 1);
2811            }
2812            None => panic!("Expected a failure"),
2813        }
2814    }
2815
2816    #[test]
2817    fn get_migration_history_on_clean_database() {
2818        let mut conn = Connection::open_in_memory().unwrap();
2819
2820        struct Migration1;
2821        impl Migration for Migration1 {
2822            fn version(&self) -> u32 {
2823                1
2824            }
2825            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
2826                Ok(())
2827            }
2828        }
2829
2830        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
2831        let history = migrator.get_migration_history(&mut conn).unwrap();
2832        assert_eq!(history.len(), 0);
2833    }
2834
2835    #[test]
2836    fn get_migration_history_after_migrations() {
2837        let mut conn = Connection::open_in_memory().unwrap();
2838
2839        struct Migration1;
2840        impl Migration for Migration1 {
2841            fn version(&self) -> u32 {
2842                1
2843            }
2844            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2845                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
2846                Ok(())
2847            }
2848            fn name(&self) -> String {
2849                "create_test1_table".to_string()
2850            }
2851        }
2852
2853        struct Migration2;
2854        impl Migration for Migration2 {
2855            fn version(&self) -> u32 {
2856                2
2857            }
2858            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2859                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
2860                Ok(())
2861            }
2862            fn name(&self) -> String {
2863                "create_test2_table".to_string()
2864            }
2865        }
2866
2867        let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
2868        migrator.upgrade(&mut conn).unwrap();
2869
2870        let history = migrator.get_migration_history(&mut conn).unwrap();
2871
2872        assert_eq!(history.len(), 2);
2873
2874        // Check first migration
2875        assert_eq!(history[0].version, 1);
2876        assert_eq!(history[0].name, "create_test1_table");
2877        assert!(history[0].applied_at.timestamp() > 0);
2878        assert_eq!(history[0].checksum.len(), 64); // SHA-256 hex string
2879
2880        // Check second migration
2881        assert_eq!(history[1].version, 2);
2882        assert_eq!(history[1].name, "create_test2_table");
2883        assert!(history[1].applied_at.timestamp() > 0);
2884        assert_eq!(history[1].checksum.len(), 64);
2885
2886        // Both should have same applied_at timestamp (same batch)
2887        assert_eq!(history[0].applied_at, history[1].applied_at);
2888    }
2889
2890    #[test]
2891    fn get_migration_history_shows_incremental_batches() {
2892        use std::thread;
2893        use std::time::Duration;
2894
2895        let mut conn = Connection::open_in_memory().unwrap();
2896
2897        struct Migration1;
2898        impl Migration for Migration1 {
2899            fn version(&self) -> u32 {
2900                1
2901            }
2902            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2903                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
2904                Ok(())
2905            }
2906            fn name(&self) -> String {
2907                "migration_one".to_string()
2908            }
2909        }
2910
2911        struct Migration2;
2912        impl Migration for Migration2 {
2913            fn version(&self) -> u32 {
2914                2
2915            }
2916            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2917                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
2918                Ok(())
2919            }
2920            fn name(&self) -> String {
2921                "migration_two".to_string()
2922            }
2923        }
2924
2925        // Run first migration
2926        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
2927        migrator.upgrade(&mut conn).unwrap();
2928
2929        let history = migrator.get_migration_history(&mut conn).unwrap();
2930        assert_eq!(history.len(), 1);
2931        let first_timestamp = history[0].applied_at;
2932
2933        // Wait a bit to ensure different timestamp
2934        thread::sleep(Duration::from_millis(2));
2935
2936        // Run second migration in a new batch
2937        let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
2938        migrator.upgrade(&mut conn).unwrap();
2939
2940        let history = migrator.get_migration_history(&mut conn).unwrap();
2941        assert_eq!(history.len(), 2);
2942
2943        // First migration timestamp should be unchanged
2944        assert_eq!(history[0].applied_at, first_timestamp);
2945
2946        // Second migration should have different timestamp
2947        assert_ne!(history[1].applied_at, first_timestamp);
2948    }
2949
2950    #[test]
2951    fn get_migration_history_includes_checksums() {
2952        let mut conn = Connection::open_in_memory().unwrap();
2953
2954        struct Migration1;
2955        impl Migration for Migration1 {
2956            fn version(&self) -> u32 {
2957                1
2958            }
2959            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2960                tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
2961                Ok(())
2962            }
2963            fn name(&self) -> String {
2964                "test_migration".to_string()
2965            }
2966        }
2967
2968        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
2969        migrator.upgrade(&mut conn).unwrap();
2970
2971        let history = migrator.get_migration_history(&mut conn).unwrap();
2972        assert_eq!(history.len(), 1);
2973
2974        // Verify checksum matches what would be calculated
2975        let migration = Box::new(Migration1) as Box<dyn Migration>;
2976        let expected_checksum = SqliteMigrator::calculate_checksum(&migration);
2977        assert_eq!(history[0].checksum, expected_checksum);
2978    }
2979
2980    #[test]
2981    fn concurrent_migrations_are_safe() {
2982        use std::sync::{Arc, Barrier};
2983        use std::thread;
2984        use tempfile::NamedTempFile;
2985
2986        // Create a temporary database file
2987        let temp_file = NamedTempFile::new().unwrap();
2988        let db_path = temp_file.path().to_str().unwrap().to_string();
2989
2990        struct Migration1;
2991        impl Migration for Migration1 {
2992            fn version(&self) -> u32 {
2993                1
2994            }
2995            fn up(&self, tx: &Transaction) -> Result<(), Error> {
2996                // Simulate some work
2997                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
2998                std::thread::sleep(std::time::Duration::from_millis(10));
2999                tx.execute("INSERT INTO test1 (id) VALUES (1)", [])?;
3000                Ok(())
3001            }
3002            fn down(&self, tx: &Transaction) -> Result<(), Error> {
3003                tx.execute("DROP TABLE test1", [])?;
3004                Ok(())
3005            }
3006        }
3007
3008        struct Migration2;
3009        impl Migration for Migration2 {
3010            fn version(&self) -> u32 {
3011                2
3012            }
3013            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3014                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
3015                std::thread::sleep(std::time::Duration::from_millis(10));
3016                tx.execute("INSERT INTO test2 (id) VALUES (1)", [])?;
3017                Ok(())
3018            }
3019            fn down(&self, tx: &Transaction) -> Result<(), Error> {
3020                tx.execute("DROP TABLE test2", [])?;
3021                Ok(())
3022            }
3023        }
3024
3025        // Create a barrier to synchronize thread starts
3026        let barrier = Arc::new(Barrier::new(3));
3027        let db_path_arc = Arc::new(db_path);
3028
3029        // Spawn 3 threads that all try to run migrations concurrently
3030        let handles: Vec<_> = (0..3)
3031            .map(|i| {
3032                let barrier = Arc::clone(&barrier);
3033                let db_path = Arc::clone(&db_path_arc);
3034                thread::spawn(move || {
3035                    // Wait for all threads to be ready
3036                    barrier.wait();
3037
3038                    // Each thread tries to upgrade
3039                    let mut conn = Connection::open(db_path.as_str()).unwrap();
3040                    let migrator =
3041                        SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
3042                    let result = migrator.upgrade(&mut conn);
3043
3044                    // All threads should succeed (busy timeout allows them to wait)
3045                    assert!(result.is_ok(), "Thread {} failed: {:?}", i, result);
3046
3047                    // Return only the migrations_run count
3048                    result.unwrap().migrations_run.len()
3049                })
3050            })
3051            .collect();
3052
3053        // Wait for all threads to complete
3054        let migrations_run_counts: Vec<_> =
3055            handles.into_iter().map(|h| h.join().unwrap()).collect();
3056
3057        let two_migrations = migrations_run_counts.iter().filter(|&&c| c == 2).count();
3058        let zero_migrations = migrations_run_counts.iter().filter(|&&c| c == 0).count();
3059
3060        assert_eq!(
3061            two_migrations, 1,
3062            "Exactly one thread should have run 2 migrations"
3063        );
3064        assert_eq!(
3065            zero_migrations, 2,
3066            "Two threads should have found db already migrated"
3067        );
3068
3069        // Verify final state
3070        let mut conn = Connection::open(db_path_arc.as_str()).unwrap();
3071        let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
3072        let current_version = migrator.get_current_version(&mut conn).unwrap();
3073        assert_eq!(current_version, 2);
3074
3075        // Verify both tables exist and have data
3076        let count1: i64 = conn
3077            .query_row("SELECT COUNT(*) FROM test1", [], |row| row.get(0))
3078            .unwrap();
3079        let count2: i64 = conn
3080            .query_row("SELECT COUNT(*) FROM test2", [], |row| row.get(0))
3081            .unwrap();
3082        assert_eq!(count1, 1);
3083        assert_eq!(count2, 1);
3084    }
3085
3086    #[test]
3087    fn concurrent_upgrade_and_downgrade() {
3088        use std::sync::{Arc, Barrier};
3089        use std::thread;
3090        use tempfile::NamedTempFile;
3091
3092        // Create a temporary database file
3093        let temp_file = NamedTempFile::new().unwrap();
3094        let db_path = temp_file.path().to_str().unwrap().to_string();
3095
3096        // First, apply some migrations
3097        {
3098            let mut conn = Connection::open(&db_path).unwrap();
3099
3100            struct Migration1;
3101            impl Migration for Migration1 {
3102                fn version(&self) -> u32 {
3103                    1
3104                }
3105                fn up(&self, tx: &Transaction) -> Result<(), Error> {
3106                    tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
3107                    Ok(())
3108                }
3109                fn down(&self, tx: &Transaction) -> Result<(), Error> {
3110                    tx.execute("DROP TABLE test1", [])?;
3111                    Ok(())
3112                }
3113            }
3114
3115            struct Migration2;
3116            impl Migration for Migration2 {
3117                fn version(&self) -> u32 {
3118                    2
3119                }
3120                fn up(&self, tx: &Transaction) -> Result<(), Error> {
3121                    tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
3122                    Ok(())
3123                }
3124                fn down(&self, tx: &Transaction) -> Result<(), Error> {
3125                    tx.execute("DROP TABLE test2", [])?;
3126                    Ok(())
3127                }
3128            }
3129
3130            let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
3131            migrator.upgrade(&mut conn).unwrap();
3132        }
3133
3134        struct Migration1;
3135        impl Migration for Migration1 {
3136            fn version(&self) -> u32 {
3137                1
3138            }
3139            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3140                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
3141                Ok(())
3142            }
3143            fn down(&self, tx: &Transaction) -> Result<(), Error> {
3144                tx.execute("DROP TABLE test1", [])?;
3145                Ok(())
3146            }
3147        }
3148
3149        struct Migration2;
3150        impl Migration for Migration2 {
3151            fn version(&self) -> u32 {
3152                2
3153            }
3154            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3155                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
3156                Ok(())
3157            }
3158            fn down(&self, tx: &Transaction) -> Result<(), Error> {
3159                tx.execute("DROP TABLE test2", [])?;
3160                Ok(())
3161            }
3162        }
3163
3164        // Now spawn threads - some upgrading, some downgrading
3165        let barrier = Arc::new(Barrier::new(4));
3166        let db_path_arc = Arc::new(db_path);
3167
3168        let handles: Vec<_> = (0..4)
3169            .map(|i| {
3170                let barrier = Arc::clone(&barrier);
3171                let db_path = Arc::clone(&db_path_arc);
3172                thread::spawn(move || {
3173                    barrier.wait();
3174
3175                    let mut conn = Connection::open(db_path.as_str()).unwrap();
3176                    let migrator =
3177                        SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
3178
3179                    let result = if i % 2 == 0 {
3180                        // Even threads try to upgrade
3181                        migrator.upgrade(&mut conn)
3182                    } else {
3183                        // Odd threads try to downgrade to version 1
3184                        migrator.downgrade(&mut conn, 1)
3185                    };
3186
3187                    // Assert success and return a simple value instead of the result
3188                    assert!(result.is_ok(), "Thread {} got error: {:?}", i, result);
3189                    true
3190                })
3191            })
3192            .collect();
3193
3194        // All threads should complete successfully without panicking
3195        for (i, handle) in handles.into_iter().enumerate() {
3196            let result = handle.join();
3197            assert!(result.is_ok(), "Thread {} panicked", i);
3198            assert!(result.unwrap(), "Thread {} failed", i);
3199        }
3200
3201        // Database should be in a valid state (either version 1 or 2)
3202        let mut conn = Connection::open(db_path_arc.as_str()).unwrap();
3203        let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
3204        let current_version = migrator.get_current_version(&mut conn).unwrap();
3205        assert!(
3206            current_version == 1 || current_version == 2,
3207            "Version should be 1 or 2, got {}",
3208            current_version
3209        );
3210    }
3211
3212    #[test]
3213    fn custom_busy_timeout() {
3214        use tempfile::NamedTempFile;
3215
3216        // Create a temporary database file
3217        let temp_file = NamedTempFile::new().unwrap();
3218        let db_path = temp_file.path().to_str().unwrap().to_string();
3219
3220        struct Migration1;
3221        impl Migration for Migration1 {
3222            fn version(&self) -> u32 {
3223                1
3224            }
3225            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3226                tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
3227                Ok(())
3228            }
3229        }
3230
3231        // Create migrator with custom 5-second timeout
3232        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)])
3233            .with_busy_timeout(std::time::Duration::from_secs(5));
3234
3235        // Verify it can run migrations successfully
3236        let mut conn = Connection::open(&db_path).unwrap();
3237        let report = migrator.upgrade(&mut conn).unwrap();
3238        assert_eq!(report.migrations_run, vec![1]);
3239
3240        // Verify the custom timeout was applied (we can't directly test the timeout value,
3241        // but we can verify the migration completed successfully with the custom setting)
3242        let current_version = migrator.get_current_version(&mut conn).unwrap();
3243        assert_eq!(current_version, 1);
3244    }
3245
3246    #[test]
3247    fn detects_missing_migration_in_code() {
3248        let mut conn = Connection::open_in_memory().unwrap();
3249
3250        struct Migration1;
3251        impl Migration for Migration1 {
3252            fn version(&self) -> u32 {
3253                1
3254            }
3255            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3256                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
3257                Ok(())
3258            }
3259            fn name(&self) -> String {
3260                "create_test1".to_string()
3261            }
3262        }
3263
3264        struct Migration2;
3265        impl Migration for Migration2 {
3266            fn version(&self) -> u32 {
3267                2
3268            }
3269            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3270                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
3271                Ok(())
3272            }
3273            fn name(&self) -> String {
3274                "create_test2".to_string()
3275            }
3276        }
3277
3278        struct Migration3;
3279        impl Migration for Migration3 {
3280            fn version(&self) -> u32 {
3281                3
3282            }
3283            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3284                tx.execute("CREATE TABLE test3 (id INTEGER PRIMARY KEY)", [])?;
3285                Ok(())
3286            }
3287            fn name(&self) -> String {
3288                "create_test3".to_string()
3289            }
3290        }
3291
3292        // Apply all three migrations
3293        let migrator = SqliteMigrator::new(vec![
3294            Box::new(Migration1),
3295            Box::new(Migration2),
3296            Box::new(Migration3),
3297        ]);
3298        migrator.upgrade(&mut conn).unwrap();
3299
3300        // Now manually delete Migration2 from the database to simulate it being removed
3301        conn.execute("DELETE FROM _migratio_version_ WHERE version = 2", [])
3302            .unwrap();
3303
3304        // Now try to run with all migrations - should detect that Migration2 was in the DB
3305        // but is now missing (we still have it in code, but DB shows it's gone)
3306        // Actually, let's simulate the opposite: Migration2 WAS applied but we removed it from code
3307        // We need to use a different Migration2 struct or manually manipulate the DB
3308
3309        // Let's manually insert a migration that doesn't exist in our code
3310        conn.execute(
3311            "INSERT INTO _migratio_version_ (version, name, applied_at, checksum) VALUES (4, 'deleted_migration', datetime('now'), 'fakechecksum')",
3312            [],
3313        )
3314        .unwrap();
3315
3316        // Now try to upgrade - should detect migration 4 exists in DB but not in code
3317        let result = migrator.upgrade(&mut conn);
3318
3319        assert!(result.is_err());
3320        let err_msg = format!("{:?}", result.unwrap_err());
3321        assert!(err_msg.contains("Migration 4"));
3322        assert!(err_msg.contains("deleted_migration"));
3323        assert!(err_msg.contains("was previously applied but is no longer present"));
3324    }
3325
3326    #[test]
3327    fn detects_orphaned_migration_added_late() {
3328        let mut conn = Connection::open_in_memory().unwrap();
3329
3330        struct Migration1;
3331        impl Migration for Migration1 {
3332            fn version(&self) -> u32 {
3333                1
3334            }
3335            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3336                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
3337                Ok(())
3338            }
3339            fn name(&self) -> String {
3340                "create_test1".to_string()
3341            }
3342        }
3343
3344        struct Migration2;
3345        impl Migration for Migration2 {
3346            fn version(&self) -> u32 {
3347                2
3348            }
3349            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3350                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
3351                Ok(())
3352            }
3353            fn name(&self) -> String {
3354                "create_test2".to_string()
3355            }
3356        }
3357
3358        struct Migration3;
3359        impl Migration for Migration3 {
3360            fn version(&self) -> u32 {
3361                3
3362            }
3363            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3364                tx.execute("CREATE TABLE test3 (id INTEGER PRIMARY KEY)", [])?;
3365                Ok(())
3366            }
3367            fn name(&self) -> String {
3368                "create_test3".to_string()
3369            }
3370        }
3371
3372        // Apply migrations 1 and 3 only (simulating migration 2 being added later)
3373        // First apply migration 1
3374        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
3375        migrator.upgrade(&mut conn).unwrap();
3376
3377        // Manually insert migration 3 into the database to simulate it being applied
3378        let checksum = {
3379            let migration = Box::new(Migration3) as Box<dyn Migration>;
3380            SqliteMigrator::calculate_checksum(&migration)
3381        };
3382        conn.execute(
3383            "INSERT INTO _migratio_version_ (version, name, applied_at, checksum) VALUES (3, 'create_test3', datetime('now'), ?1)",
3384            [&checksum],
3385        )
3386        .unwrap();
3387
3388        // Now try to upgrade with all three migrations
3389        // This should fail because migration 2 exists in code but wasn't applied,
3390        // yet migration 3 (which comes after it) was already applied
3391        let migrator_all = SqliteMigrator::new(vec![
3392            Box::new(Migration1),
3393            Box::new(Migration2),
3394            Box::new(Migration3),
3395        ]);
3396        let result = migrator_all.upgrade(&mut conn);
3397
3398        assert!(result.is_err());
3399        let err_msg = format!("{:?}", result.unwrap_err());
3400        assert!(err_msg.contains("Migration 2"));
3401        assert!(err_msg.contains("create_test2"));
3402        assert!(err_msg.contains("exists in code but was not applied"));
3403        assert!(err_msg.contains("later migrations are already applied"));
3404    }
3405
3406    #[test]
3407    fn detects_missing_migration_during_downgrade() {
3408        let mut conn = Connection::open_in_memory().unwrap();
3409
3410        struct Migration1;
3411        impl Migration for Migration1 {
3412            fn version(&self) -> u32 {
3413                1
3414            }
3415            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3416                tx.execute("CREATE TABLE test1 (id INTEGER PRIMARY KEY)", [])?;
3417                Ok(())
3418            }
3419            fn down(&self, tx: &Transaction) -> Result<(), Error> {
3420                tx.execute("DROP TABLE test1", [])?;
3421                Ok(())
3422            }
3423            fn name(&self) -> String {
3424                "create_test1".to_string()
3425            }
3426        }
3427
3428        struct Migration2;
3429        impl Migration for Migration2 {
3430            fn version(&self) -> u32 {
3431                2
3432            }
3433            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3434                tx.execute("CREATE TABLE test2 (id INTEGER PRIMARY KEY)", [])?;
3435                Ok(())
3436            }
3437            fn down(&self, tx: &Transaction) -> Result<(), Error> {
3438                tx.execute("DROP TABLE test2", [])?;
3439                Ok(())
3440            }
3441            fn name(&self) -> String {
3442                "create_test2".to_string()
3443            }
3444        }
3445
3446        // Apply both migrations
3447        let migrator = SqliteMigrator::new(vec![Box::new(Migration1), Box::new(Migration2)]);
3448        migrator.upgrade(&mut conn).unwrap();
3449
3450        // Manually insert a migration that doesn't exist in code
3451        conn.execute(
3452            "INSERT INTO _migratio_version_ (version, name, applied_at, checksum) VALUES (3, 'orphaned_migration', datetime('now'), 'fakechecksum')",
3453            [],
3454        )
3455        .unwrap();
3456
3457        // Try to downgrade - should detect migration 3 exists in DB but not in code
3458        let result = migrator.downgrade(&mut conn, 0);
3459
3460        assert!(result.is_err());
3461        let err_msg = format!("{:?}", result.unwrap_err());
3462        assert!(err_msg.contains("Migration 3"));
3463        assert!(err_msg.contains("orphaned_migration"));
3464        assert!(err_msg.contains("was previously applied but is no longer present"));
3465    }
3466
3467    #[test]
3468    fn hooks_are_called_on_successful_migration() {
3469        use std::sync::{Arc, Mutex};
3470
3471        let mut conn = Connection::open_in_memory().unwrap();
3472
3473        struct Migration1;
3474        impl Migration for Migration1 {
3475            fn version(&self) -> u32 {
3476                1
3477            }
3478            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3479                tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
3480                Ok(())
3481            }
3482            fn name(&self) -> String {
3483                "test_migration".to_string()
3484            }
3485        }
3486
3487        let start_calls = Arc::new(Mutex::new(Vec::new()));
3488        let complete_calls = Arc::new(Mutex::new(Vec::new()));
3489        let error_calls = Arc::new(Mutex::new(Vec::new()));
3490
3491        let start_calls_clone = Arc::clone(&start_calls);
3492        let complete_calls_clone = Arc::clone(&complete_calls);
3493        let error_calls_clone = Arc::clone(&error_calls);
3494
3495        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)])
3496            .on_migration_start(move |version, name| {
3497                start_calls_clone
3498                    .lock()
3499                    .unwrap()
3500                    .push((version, name.to_string()));
3501            })
3502            .on_migration_complete(move |version, name, duration| {
3503                complete_calls_clone
3504                    .lock()
3505                    .unwrap()
3506                    .push((version, name.to_string(), duration));
3507            })
3508            .on_migration_error(move |version, name, error| {
3509                error_calls_clone.lock().unwrap().push((
3510                    version,
3511                    name.to_string(),
3512                    format!("{:?}", error),
3513                ));
3514            });
3515
3516        migrator.upgrade(&mut conn).unwrap();
3517
3518        // Verify hooks were called correctly
3519        let starts = start_calls.lock().unwrap();
3520        assert_eq!(starts.len(), 1);
3521        assert_eq!(starts[0], (1, "test_migration".to_string()));
3522
3523        let completes = complete_calls.lock().unwrap();
3524        assert_eq!(completes.len(), 1);
3525        assert_eq!(completes[0].0, 1);
3526        assert_eq!(completes[0].1, "test_migration");
3527        // Duration is always non-negative, just verify it exists
3528        let _ = completes[0].2;
3529
3530        let errors = error_calls.lock().unwrap();
3531        assert_eq!(errors.len(), 0); // No errors should have been called
3532    }
3533
3534    #[test]
3535    fn hooks_are_called_on_failed_migration() {
3536        use std::sync::{Arc, Mutex};
3537
3538        let mut conn = Connection::open_in_memory().unwrap();
3539
3540        struct Migration1;
3541        impl Migration for Migration1 {
3542            fn version(&self) -> u32 {
3543                1
3544            }
3545            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3546                tx.execute("INVALID SQL", [])?;
3547                Ok(())
3548            }
3549            fn name(&self) -> String {
3550                "failing_migration".to_string()
3551            }
3552        }
3553
3554        let start_calls = Arc::new(Mutex::new(Vec::new()));
3555        let complete_calls = Arc::new(Mutex::new(Vec::new()));
3556        let error_calls = Arc::new(Mutex::new(Vec::new()));
3557
3558        let start_calls_clone = Arc::clone(&start_calls);
3559        let complete_calls_clone = Arc::clone(&complete_calls);
3560        let error_calls_clone = Arc::clone(&error_calls);
3561
3562        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)])
3563            .on_migration_start(move |version, name| {
3564                start_calls_clone
3565                    .lock()
3566                    .unwrap()
3567                    .push((version, name.to_string()));
3568            })
3569            .on_migration_complete(move |version, name, duration| {
3570                complete_calls_clone
3571                    .lock()
3572                    .unwrap()
3573                    .push((version, name.to_string(), duration));
3574            })
3575            .on_migration_error(move |version, name, _error| {
3576                error_calls_clone
3577                    .lock()
3578                    .unwrap()
3579                    .push((version, name.to_string()));
3580            });
3581
3582        let _ = migrator.upgrade(&mut conn); // Expect this to fail
3583
3584        // Verify hooks were called correctly
3585        let starts = start_calls.lock().unwrap();
3586        assert_eq!(starts.len(), 1);
3587        assert_eq!(starts[0], (1, "failing_migration".to_string()));
3588
3589        let completes = complete_calls.lock().unwrap();
3590        assert_eq!(completes.len(), 0); // Complete should not be called on error
3591
3592        let errors = error_calls.lock().unwrap();
3593        assert_eq!(errors.len(), 1);
3594        assert_eq!(errors[0], (1, "failing_migration".to_string()));
3595    }
3596
3597    #[test]
3598    fn hooks_are_called_on_downgrade() {
3599        use std::sync::{Arc, Mutex};
3600
3601        let mut conn = Connection::open_in_memory().unwrap();
3602
3603        struct Migration1;
3604        impl Migration for Migration1 {
3605            fn version(&self) -> u32 {
3606                1
3607            }
3608            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3609                tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
3610                Ok(())
3611            }
3612            fn down(&self, tx: &Transaction) -> Result<(), Error> {
3613                tx.execute("DROP TABLE test", [])?;
3614                Ok(())
3615            }
3616            fn name(&self) -> String {
3617                "test_migration".to_string()
3618            }
3619        }
3620
3621        // First apply the migration
3622        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
3623        migrator.upgrade(&mut conn).unwrap();
3624
3625        // Now set up hooks for downgrade
3626        let start_calls = Arc::new(Mutex::new(Vec::new()));
3627        let complete_calls = Arc::new(Mutex::new(Vec::new()));
3628
3629        let start_calls_clone = Arc::clone(&start_calls);
3630        let complete_calls_clone = Arc::clone(&complete_calls);
3631
3632        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)])
3633            .on_migration_start(move |version, name| {
3634                start_calls_clone
3635                    .lock()
3636                    .unwrap()
3637                    .push((version, name.to_string()));
3638            })
3639            .on_migration_complete(move |version, name, duration| {
3640                complete_calls_clone
3641                    .lock()
3642                    .unwrap()
3643                    .push((version, name.to_string(), duration));
3644            });
3645
3646        migrator.downgrade(&mut conn, 0).unwrap();
3647
3648        // Verify hooks were called
3649        let starts = start_calls.lock().unwrap();
3650        assert_eq!(starts.len(), 1);
3651        assert_eq!(starts[0], (1, "test_migration".to_string()));
3652
3653        let completes = complete_calls.lock().unwrap();
3654        assert_eq!(completes.len(), 1);
3655        assert_eq!(completes[0].0, 1);
3656        assert_eq!(completes[0].1, "test_migration");
3657    }
3658
3659    #[test]
3660    #[cfg(feature = "tracing")]
3661    fn tracing_logs_successful_migration() {
3662        use tracing_test::traced_test;
3663
3664        #[traced_test]
3665        fn run_test() {
3666            let mut conn = Connection::open_in_memory().unwrap();
3667
3668            struct Migration1;
3669            impl Migration for Migration1 {
3670                fn version(&self) -> u32 {
3671                    1
3672                }
3673                fn up(&self, tx: &Transaction) -> Result<(), Error> {
3674                    tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
3675                    Ok(())
3676                }
3677                fn name(&self) -> String {
3678                    "test_migration".to_string()
3679                }
3680            }
3681
3682            let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
3683            migrator.upgrade(&mut conn).unwrap();
3684
3685            // Verify tracing logs were emitted
3686            assert!(logs_contain("Starting migration"));
3687            assert!(logs_contain("Migration completed successfully"));
3688            assert!(logs_contain("duration_ms"));
3689        }
3690
3691        run_test();
3692    }
3693
3694    #[test]
3695    #[cfg(feature = "tracing")]
3696    fn tracing_logs_failed_migration() {
3697        use tracing_test::traced_test;
3698
3699        #[traced_test]
3700        fn run_test() {
3701            let mut conn = Connection::open_in_memory().unwrap();
3702
3703            struct Migration1;
3704            impl Migration for Migration1 {
3705                fn version(&self) -> u32 {
3706                    1
3707                }
3708                fn up(&self, tx: &Transaction) -> Result<(), Error> {
3709                    tx.execute("INVALID SQL", [])?;
3710                    Ok(())
3711                }
3712                fn name(&self) -> String {
3713                    "failing_migration".to_string()
3714                }
3715            }
3716
3717            let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
3718            let _ = migrator.upgrade(&mut conn);
3719
3720            // Verify tracing error logs were emitted
3721            assert!(logs_contain("Starting migration"));
3722            assert!(logs_contain("Migration failed"));
3723        }
3724
3725        run_test();
3726    }
3727
3728    #[test]
3729    #[cfg(feature = "tracing")]
3730    fn tracing_logs_downgrade() {
3731        use tracing_test::traced_test;
3732
3733        #[traced_test]
3734        fn run_test() {
3735            let mut conn = Connection::open_in_memory().unwrap();
3736
3737            struct Migration1;
3738            impl Migration for Migration1 {
3739                fn version(&self) -> u32 {
3740                    1
3741                }
3742                fn up(&self, tx: &Transaction) -> Result<(), Error> {
3743                    tx.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)", [])?;
3744                    Ok(())
3745                }
3746                fn down(&self, tx: &Transaction) -> Result<(), Error> {
3747                    tx.execute("DROP TABLE test", [])?;
3748                    Ok(())
3749                }
3750                fn name(&self) -> String {
3751                    "test_migration".to_string()
3752                }
3753            }
3754
3755            let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
3756            migrator.upgrade(&mut conn).unwrap();
3757            migrator.downgrade(&mut conn, 0).unwrap();
3758
3759            // Verify tracing logs for downgrade were emitted
3760            assert!(logs_contain("Rolling back migration"));
3761            assert!(logs_contain("Migration rolled back successfully"));
3762        }
3763
3764        run_test();
3765    }
3766
3767    #[test]
3768    fn precondition_already_satisfied() {
3769        use std::sync::{Arc, Mutex};
3770
3771        let mut conn = Connection::open_in_memory().unwrap();
3772
3773        // Create table manually (simulating existing migration from another tool)
3774        conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY)", [])
3775            .unwrap();
3776
3777        // Track whether up() was called
3778        let up_called = Arc::new(Mutex::new(false));
3779        let up_called_clone = Arc::clone(&up_called);
3780
3781        struct Migration1 {
3782            up_called: Arc<Mutex<bool>>,
3783        }
3784        impl Migration for Migration1 {
3785            fn version(&self) -> u32 {
3786                1
3787            }
3788            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3789                // Mark that up() was called
3790                *self.up_called.lock().unwrap() = true;
3791                tx.execute("CREATE TABLE users (id INTEGER PRIMARY KEY)", [])?;
3792                Ok(())
3793            }
3794            fn precondition(&self, tx: &Transaction) -> Result<Precondition, Error> {
3795                // Check if table already exists
3796                let mut stmt = tx.prepare(
3797                    "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='users'",
3798                )?;
3799                let count: i64 = stmt.query_row([], |row| row.get(0))?;
3800
3801                if count > 0 {
3802                    Ok(Precondition::AlreadySatisfied)
3803                } else {
3804                    Ok(Precondition::NeedsApply)
3805                }
3806            }
3807        }
3808
3809        let migrator = SqliteMigrator::new(vec![Box::new(Migration1 {
3810            up_called: up_called_clone,
3811        })]);
3812
3813        let report = migrator.upgrade(&mut conn).unwrap();
3814
3815        // Verify migration was recorded as run
3816        assert_eq!(report.migrations_run, vec![1]);
3817        assert!(report.failing_migration.is_none());
3818
3819        // Verify up() was NOT called
3820        assert!(!*up_called.lock().unwrap());
3821
3822        // Verify migration is in version table
3823        let version = migrator.get_current_version(&mut conn).unwrap();
3824        assert_eq!(version, 1);
3825
3826        // Verify table still exists (wasn't modified)
3827        let table_exists: i64 = conn
3828            .query_row(
3829                "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='users'",
3830                [],
3831                |row| row.get(0),
3832            )
3833            .unwrap();
3834        assert_eq!(table_exists, 1);
3835    }
3836
3837    #[test]
3838    fn precondition_needs_apply() {
3839        use std::sync::{Arc, Mutex};
3840
3841        let mut conn = Connection::open_in_memory().unwrap();
3842
3843        // Track whether up() was called
3844        let up_called = Arc::new(Mutex::new(false));
3845        let up_called_clone = Arc::clone(&up_called);
3846
3847        struct Migration1 {
3848            up_called: Arc<Mutex<bool>>,
3849        }
3850        impl Migration for Migration1 {
3851            fn version(&self) -> u32 {
3852                1
3853            }
3854            fn up(&self, tx: &Transaction) -> Result<(), Error> {
3855                // Mark that up() was called
3856                *self.up_called.lock().unwrap() = true;
3857                tx.execute("CREATE TABLE users (id INTEGER PRIMARY KEY)", [])?;
3858                Ok(())
3859            }
3860            fn precondition(&self, tx: &Transaction) -> Result<Precondition, Error> {
3861                // Check if table already exists
3862                let mut stmt = tx.prepare(
3863                    "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='users'",
3864                )?;
3865                let count: i64 = stmt.query_row([], |row| row.get(0))?;
3866
3867                if count > 0 {
3868                    Ok(Precondition::AlreadySatisfied)
3869                } else {
3870                    Ok(Precondition::NeedsApply)
3871                }
3872            }
3873        }
3874
3875        let migrator = SqliteMigrator::new(vec![Box::new(Migration1 {
3876            up_called: up_called_clone,
3877        })]);
3878
3879        let report = migrator.upgrade(&mut conn).unwrap();
3880
3881        // Verify migration was recorded as run
3882        assert_eq!(report.migrations_run, vec![1]);
3883        assert!(report.failing_migration.is_none());
3884
3885        // Verify up() WAS called
3886        assert!(*up_called.lock().unwrap());
3887
3888        // Verify migration is in version table
3889        let version = migrator.get_current_version(&mut conn).unwrap();
3890        assert_eq!(version, 1);
3891
3892        // Verify table was created
3893        let table_exists: i64 = conn
3894            .query_row(
3895                "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='users'",
3896                [],
3897                |row| row.get(0),
3898            )
3899            .unwrap();
3900        assert_eq!(table_exists, 1);
3901    }
3902
3903    #[test]
3904    fn precondition_error() {
3905        let mut conn = Connection::open_in_memory().unwrap();
3906
3907        struct Migration1;
3908        impl Migration for Migration1 {
3909            fn version(&self) -> u32 {
3910                1
3911            }
3912            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
3913                Ok(())
3914            }
3915            fn precondition(&self, _tx: &Transaction) -> Result<Precondition, Error> {
3916                // Simulate a precondition check error
3917                Err(rusqlite::Error::InvalidQuery.into())
3918            }
3919        }
3920
3921        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)]);
3922        let report = migrator.upgrade(&mut conn).unwrap();
3923
3924        // Verify migration failed
3925        assert_eq!(report.migrations_run, vec![]);
3926        assert!(report.failing_migration.is_some());
3927
3928        let failure = report.failing_migration.unwrap();
3929        assert_eq!(failure.migration().version(), 1);
3930
3931        // Verify migration is NOT in version table
3932        let version = migrator.get_current_version(&mut conn).unwrap();
3933        assert_eq!(version, 0);
3934    }
3935
3936    #[test]
3937    fn precondition_hooks_already_satisfied() {
3938        use std::sync::{Arc, Mutex};
3939
3940        let mut conn = Connection::open_in_memory().unwrap();
3941
3942        // Create table manually (simulating existing migration from another tool)
3943        conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY)", [])
3944            .unwrap();
3945
3946        // Track hook calls
3947        let events = Arc::new(Mutex::new(Vec::new()));
3948        let events_clone1 = Arc::clone(&events);
3949        let events_clone2 = Arc::clone(&events);
3950        let events_clone3 = Arc::clone(&events);
3951        let events_clone4 = Arc::clone(&events);
3952
3953        struct Migration1;
3954        impl Migration for Migration1 {
3955            fn version(&self) -> u32 {
3956                1
3957            }
3958            fn up(&self, _tx: &Transaction) -> Result<(), Error> {
3959                // This should NOT be called
3960                panic!("up() should not be called when precondition is AlreadySatisfied");
3961            }
3962            fn precondition(&self, tx: &Transaction) -> Result<Precondition, Error> {
3963                // Check if table already exists
3964                let mut stmt = tx.prepare(
3965                    "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='users'",
3966                )?;
3967                let count: i64 = stmt.query_row([], |row| row.get(0))?;
3968
3969                if count > 0 {
3970                    Ok(Precondition::AlreadySatisfied)
3971                } else {
3972                    Ok(Precondition::NeedsApply)
3973                }
3974            }
3975        }
3976
3977        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)])
3978            .on_migration_start(move |version, name| {
3979                events_clone1
3980                    .lock()
3981                    .unwrap()
3982                    .push(format!("start:{}:{}", version, name));
3983            })
3984            .on_migration_skipped(move |version, name| {
3985                events_clone2
3986                    .lock()
3987                    .unwrap()
3988                    .push(format!("skipped:{}:{}", version, name));
3989            })
3990            .on_migration_complete(move |version, name, _duration| {
3991                events_clone3
3992                    .lock()
3993                    .unwrap()
3994                    .push(format!("complete:{}:{}", version, name));
3995            })
3996            .on_migration_error(move |version, name, _error| {
3997                events_clone4
3998                    .lock()
3999                    .unwrap()
4000                    .push(format!("error:{}:{}", version, name));
4001            });
4002
4003        let report = migrator.upgrade(&mut conn).unwrap();
4004
4005        // Verify migration was recorded
4006        assert_eq!(report.migrations_run, vec![1]);
4007        assert!(report.failing_migration.is_none());
4008
4009        // Verify hooks were called correctly
4010        let events_vec = events.lock().unwrap();
4011        assert_eq!(events_vec.len(), 3);
4012        assert_eq!(events_vec[0], "start:1:Migration 1");
4013        assert_eq!(events_vec[1], "skipped:1:Migration 1");
4014        assert_eq!(events_vec[2], "complete:1:Migration 1");
4015    }
4016
4017    #[test]
4018    fn precondition_hooks_needs_apply() {
4019        use std::sync::{Arc, Mutex};
4020
4021        let mut conn = Connection::open_in_memory().unwrap();
4022
4023        // Track hook calls
4024        let events = Arc::new(Mutex::new(Vec::new()));
4025        let events_clone1 = Arc::clone(&events);
4026        let events_clone2 = Arc::clone(&events);
4027        let events_clone3 = Arc::clone(&events);
4028        let events_clone4 = Arc::clone(&events);
4029
4030        struct Migration1;
4031        impl Migration for Migration1 {
4032            fn version(&self) -> u32 {
4033                1
4034            }
4035            fn up(&self, tx: &Transaction) -> Result<(), Error> {
4036                tx.execute("CREATE TABLE users (id INTEGER PRIMARY KEY)", [])?;
4037                Ok(())
4038            }
4039            fn precondition(&self, tx: &Transaction) -> Result<Precondition, Error> {
4040                // Check if table already exists
4041                let mut stmt = tx.prepare(
4042                    "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='users'",
4043                )?;
4044                let count: i64 = stmt.query_row([], |row| row.get(0))?;
4045
4046                if count > 0 {
4047                    Ok(Precondition::AlreadySatisfied)
4048                } else {
4049                    Ok(Precondition::NeedsApply)
4050                }
4051            }
4052        }
4053
4054        let migrator = SqliteMigrator::new(vec![Box::new(Migration1)])
4055            .on_migration_start(move |version, name| {
4056                events_clone1
4057                    .lock()
4058                    .unwrap()
4059                    .push(format!("start:{}:{}", version, name));
4060            })
4061            .on_migration_skipped(move |version, name| {
4062                events_clone2
4063                    .lock()
4064                    .unwrap()
4065                    .push(format!("skipped:{}:{}", version, name));
4066            })
4067            .on_migration_complete(move |version, name, _duration| {
4068                events_clone3
4069                    .lock()
4070                    .unwrap()
4071                    .push(format!("complete:{}:{}", version, name));
4072            })
4073            .on_migration_error(move |version, name, _error| {
4074                events_clone4
4075                    .lock()
4076                    .unwrap()
4077                    .push(format!("error:{}:{}", version, name));
4078            });
4079
4080        let report = migrator.upgrade(&mut conn).unwrap();
4081
4082        // Verify migration was recorded
4083        assert_eq!(report.migrations_run, vec![1]);
4084        assert!(report.failing_migration.is_none());
4085
4086        // Verify hooks were called correctly (no skipped hook)
4087        let events_vec = events.lock().unwrap();
4088        assert_eq!(events_vec.len(), 2);
4089        assert_eq!(events_vec[0], "start:1:Migration 1");
4090        assert_eq!(events_vec[1], "complete:1:Migration 1");
4091    }
4092
4093    #[test]
4094    fn precondition_mixed_migrations() {
4095        use std::sync::{Arc, Mutex};
4096
4097        let mut conn = Connection::open_in_memory().unwrap();
4098
4099        // Create one table manually (simulating partial migration state)
4100        conn.execute("CREATE TABLE table1 (id INTEGER PRIMARY KEY)", [])
4101            .unwrap();
4102
4103        // Track which migrations had up() called
4104        let up_calls = Arc::new(Mutex::new(Vec::new()));
4105        let up_calls_clone1 = Arc::clone(&up_calls);
4106        let up_calls_clone2 = Arc::clone(&up_calls);
4107        let up_calls_clone3 = Arc::clone(&up_calls);
4108
4109        struct Migration1 {
4110            up_calls: Arc<Mutex<Vec<u32>>>,
4111        }
4112        impl Migration for Migration1 {
4113            fn version(&self) -> u32 {
4114                1
4115            }
4116            fn up(&self, tx: &Transaction) -> Result<(), Error> {
4117                self.up_calls.lock().unwrap().push(1);
4118                tx.execute("CREATE TABLE table1 (id INTEGER PRIMARY KEY)", [])?;
4119                Ok(())
4120            }
4121            fn precondition(&self, tx: &Transaction) -> Result<Precondition, Error> {
4122                let mut stmt = tx.prepare(
4123                    "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='table1'",
4124                )?;
4125                let count: i64 = stmt.query_row([], |row| row.get(0))?;
4126                Ok(if count > 0 {
4127                    Precondition::AlreadySatisfied
4128                } else {
4129                    Precondition::NeedsApply
4130                })
4131            }
4132        }
4133
4134        struct Migration2 {
4135            up_calls: Arc<Mutex<Vec<u32>>>,
4136        }
4137        impl Migration for Migration2 {
4138            fn version(&self) -> u32 {
4139                2
4140            }
4141            fn up(&self, tx: &Transaction) -> Result<(), Error> {
4142                self.up_calls.lock().unwrap().push(2);
4143                tx.execute("CREATE TABLE table2 (id INTEGER PRIMARY KEY)", [])?;
4144                Ok(())
4145            }
4146            fn precondition(&self, tx: &Transaction) -> Result<Precondition, Error> {
4147                let mut stmt = tx.prepare(
4148                    "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='table2'",
4149                )?;
4150                let count: i64 = stmt.query_row([], |row| row.get(0))?;
4151                Ok(if count > 0 {
4152                    Precondition::AlreadySatisfied
4153                } else {
4154                    Precondition::NeedsApply
4155                })
4156            }
4157        }
4158
4159        struct Migration3 {
4160            up_calls: Arc<Mutex<Vec<u32>>>,
4161        }
4162        impl Migration for Migration3 {
4163            fn version(&self) -> u32 {
4164                3
4165            }
4166            fn up(&self, tx: &Transaction) -> Result<(), Error> {
4167                self.up_calls.lock().unwrap().push(3);
4168                tx.execute("CREATE TABLE table3 (id INTEGER PRIMARY KEY)", [])?;
4169                Ok(())
4170            }
4171            fn precondition(&self, tx: &Transaction) -> Result<Precondition, Error> {
4172                let mut stmt = tx.prepare(
4173                    "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='table3'",
4174                )?;
4175                let count: i64 = stmt.query_row([], |row| row.get(0))?;
4176                Ok(if count > 0 {
4177                    Precondition::AlreadySatisfied
4178                } else {
4179                    Precondition::NeedsApply
4180                })
4181            }
4182        }
4183
4184        let migrator = SqliteMigrator::new(vec![
4185            Box::new(Migration1 {
4186                up_calls: up_calls_clone1,
4187            }),
4188            Box::new(Migration2 {
4189                up_calls: up_calls_clone2,
4190            }),
4191            Box::new(Migration3 {
4192                up_calls: up_calls_clone3,
4193            }),
4194        ]);
4195
4196        let report = migrator.upgrade(&mut conn).unwrap();
4197
4198        // Verify all migrations were recorded
4199        assert_eq!(report.migrations_run, vec![1, 2, 3]);
4200        assert!(report.failing_migration.is_none());
4201
4202        // Verify only migrations 2 and 3 had up() called (migration 1 was skipped)
4203        let calls = up_calls.lock().unwrap();
4204        assert_eq!(*calls, vec![2, 3]);
4205
4206        // Verify all tables exist
4207        let table_count: i64 = conn
4208            .query_row(
4209                "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name IN ('table1', 'table2', 'table3')",
4210                [],
4211                |row| row.get(0),
4212            )
4213            .unwrap();
4214        assert_eq!(table_count, 3);
4215
4216        // Verify all migrations are in version table
4217        let version = migrator.get_current_version(&mut conn).unwrap();
4218        assert_eq!(version, 3);
4219    }
4220}