rusqlite_migration/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright Clément Joly and contributors.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//    http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#![cfg_attr(docsrs, feature(doc_auto_cfg))]
17// The doc is extracted from the README.md file at build time
18#![doc = include_str!(concat!(env!("OUT_DIR"), "/readme_for_rustdoc.md"))]
19
20use std::borrow::Cow;
21use std::fmt::Display;
22
23use log::{debug, info, trace, warn};
24use rusqlite::{Connection, Transaction};
25
26#[cfg(feature = "from-directory")]
27use include_dir::Dir;
28
29#[cfg(feature = "from-directory")]
30mod loader;
31#[cfg(feature = "from-directory")]
32use loader::from_directory;
33
34#[cfg(feature = "from-directory")]
35mod builder;
36#[cfg(feature = "from-directory")]
37pub use builder::MigrationsBuilder;
38
39mod errors;
40
41#[cfg(test)]
42mod tests;
43
44pub use errors::{
45    Error, ForeignKeyCheckError, HookError, HookResult, MigrationDefinitionError, Result,
46    SchemaVersionError,
47};
48use std::{
49    cmp::{self, Ordering},
50    fmt::{self, Debug},
51    iter::FromIterator,
52    num::NonZeroUsize,
53    ptr::addr_of,
54};
55
56/// The number of migrations already applied is stored in a [4 bytes field][sqlite_doc], so the number of migrations is limited.
57///
58/// [sqlite_doc]: https://www.sqlite.org/fileformat.html#user_version_number
59pub const MIGRATIONS_MAX: usize = i32::MAX as usize;
60
61/// Helper trait to make hook functions cloneable.
62pub trait MigrationHook: Fn(&Transaction) -> HookResult + Send + Sync {
63    /// Clone self.
64    fn clone_box(&self) -> Box<dyn MigrationHook>;
65}
66
67impl<T> MigrationHook for T
68where
69    T: 'static + Clone + Send + Sync + Fn(&Transaction) -> HookResult,
70{
71    fn clone_box(&self) -> Box<dyn MigrationHook> {
72        Box::new(self.clone())
73    }
74}
75
76impl Debug for Box<dyn MigrationHook> {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        // Don’t print the closure address as it changes between runs
79        write!(f, "MigrationHook(<closure>)")
80    }
81}
82
83impl Clone for Box<dyn MigrationHook> {
84    fn clone(&self) -> Self {
85        (**self).clone_box()
86    }
87}
88
89/// One migration.
90///
91/// A migration can contain up- and down-hooks, which are incomparable closures.
92/// To signify `M` equality we compare if two migrations either don't have hooks defined (they are set to `None`)
93/// or if the closure memory addresses are the same.
94#[derive(Debug, Clone)]
95#[must_use]
96pub struct M<'u> {
97    up: &'u str,
98    up_hook: Option<Box<dyn MigrationHook>>,
99    down: Option<&'u str>,
100    down_hook: Option<Box<dyn MigrationHook>>,
101    foreign_key_check: bool,
102    comment: Option<&'u str>,
103}
104
105impl Display for M<'_> {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        let M {
108            up,
109            up_hook,
110            down,
111            down_hook,
112            foreign_key_check,
113            comment,
114        } = self;
115        let nl = if f.alternate() { "\n" } else { "" };
116        let ind = if f.alternate() { "\n    " } else { "" };
117        write!(f, r#"M({ind}up: "{up}""#)?;
118        if up_hook.is_some() {
119            write!(f, ", {ind}up hook")?;
120        }
121        if let Some(down) = down {
122            write!(f, r#", {ind}down: "{down}""#)?;
123        }
124        if down_hook.is_some() {
125            write!(f, ", {ind}down hook")?;
126        }
127        if *foreign_key_check {
128            write!(f, ", {ind}foreign key check")?;
129        }
130        if let Some(comment) = comment {
131            write!(f, r#", {ind}comment: "{comment}""#)?;
132        }
133        write!(f, "{nl})")
134    }
135}
136
137impl PartialEq for M<'_> {
138    fn eq(&self, other: &Self) -> bool {
139        use std::ptr;
140
141        let equal_up_hooks = match (self.up_hook.as_ref(), other.up_hook.as_ref()) {
142            (None, None) => true,
143            (Some(a), Some(b)) => ptr::eq(addr_of!(*a), addr_of!(*b)),
144            _ => false,
145        };
146
147        let equal_down_hooks = match (self.down_hook.as_ref(), other.down_hook.as_ref()) {
148            (None, None) => true,
149            (Some(a), Some(b)) => ptr::eq(addr_of!(*a), addr_of!(*b)),
150            _ => false,
151        };
152
153        self.up == other.up
154            && self.down == other.down
155            && equal_up_hooks
156            && equal_down_hooks
157            && self.foreign_key_check == other.foreign_key_check
158    }
159}
160
161impl Eq for M<'_> {}
162
163impl<'u> M<'u> {
164    /// Create a schema update. The SQL command will be executed only when the migration has not been
165    /// executed on the underlying database.
166    ///
167    /// # Please note
168    ///
169    /// ## PRAGMA statements
170    ///
171    /// PRAGMA statements are discouraged here. They are often better applied outside of
172    /// migrations, because:
173    ///   * a PRAGMA executed this way may not be applied consistently. For instance:
174    ///     * [`foreign_keys`](https://sqlite.org/pragma.html#pragma_foreign_keys) needs to be
175    ///       executed for each sqlite connection, not just once per database as a migration. Please
176    ///       see the [`Self::foreign_key_check()`] method to maintain foreign key constraints during
177    ///       migrations instead.
178    ///     * [`journal_mode`][jm] has no effect when executed inside transactions (that will be
179    ///       the case for the SQL written in `up`).
180    ///   * Multiple SQL commands containing `PRAGMA` are [not working][ru794] with the
181    ///     `extra_check` feature of rusqlite.
182    ///
183    /// ## Misc.
184    ///
185    /// * SQL commands should end with a “;”.
186    /// * You can use the `include_str!` macro to include whole files or opt for the
187    ///   `from-directory` feature of this crate.
188    ///
189    /// # Example
190    ///
191    /// ```
192    /// use rusqlite_migration::M;
193    ///
194    /// M::up("CREATE TABLE animals (name TEXT);");
195    /// ```
196    ///
197    /// [ru794]: https://github.com/rusqlite/rusqlite/pull/794
198    /// [jm]: https://sqlite.org/pragma.html#pragma_journal_mode
199    pub const fn up(sql: &'u str) -> Self {
200        Self {
201            up: sql,
202            up_hook: None,
203            down: None,
204            down_hook: None,
205            foreign_key_check: false,
206            comment: None,
207        }
208    }
209
210    /// Add a comment to the schema update
211    pub const fn comment(mut self, comment: &'u str) -> Self {
212        self.comment = Some(comment);
213        self
214    }
215
216    /// Create a schema update running additional Rust code. The SQL command will be executed only
217    /// when the migration has not been executed on the underlying database. The `hook` code will
218    /// be executed *after* the SQL command executed successfully.
219    ///
220    /// See [`Self::up()`] for additional notes.
221    ///
222    /// # Example
223    ///
224    /// ```
225    /// use rusqlite_migration::{M, Migrations};
226    /// use rusqlite::Transaction;
227    ///
228    /// let migrations = Migrations::new(vec![
229    ///     // This table will later be filled with some novel content
230    ///     M::up("CREATE TABLE novels (text TEXT);"),
231    ///     M::up_with_hook(
232    ///         "ALTER TABLE novels ADD compressed TEXT;",
233    ///         |tx: &Transaction| {
234    ///             let mut stmt = tx.prepare("SELECT rowid, text FROM novels")?;
235    ///             let rows = stmt
236    ///                 .query_map([], |row| {
237    ///                     Ok((row.get_unwrap::<_, i64>(0), row.get_unwrap::<_, String>(1)))
238    ///                 })?;
239    ///
240    ///             for row in rows {
241    ///                 let row = row?;
242    ///                 let rowid = row.0;
243    ///                 let text = row.1;
244    ///                 // Replace with a proper compression strategy ...
245    ///                 let compressed = &text[..text.len() / 2];
246    ///                 tx.execute(
247    ///                     "UPDATE novels SET compressed = ?1 WHERE rowid = ?2;",
248    ///                     rusqlite::params![compressed, rowid],
249    ///                 )?;
250    ///             }
251    ///
252    ///             Ok(())
253    ///         },
254    ///     ),
255    /// ]);
256    /// ```
257    pub fn up_with_hook(sql: &'u str, hook: impl MigrationHook + 'static) -> Self {
258        let mut m = Self::up(sql);
259        m.up_hook = Some(hook.clone_box());
260        m
261    }
262
263    /// Define a down-migration. This SQL statement should exactly reverse the changes
264    /// performed in `up()`.
265    ///
266    /// A call to this method is **not** required.
267    ///
268    /// # Example
269    ///
270    /// ```
271    /// use rusqlite_migration::M;
272    ///
273    /// M::up("CREATE TABLE animals (name TEXT);")
274    ///     .down("DROP TABLE animals;");
275    /// ```
276    pub const fn down(mut self, sql: &'u str) -> Self {
277        self.down = Some(sql);
278        self
279    }
280
281    /// Define a down-migration running additional Rust code. This SQL statement should exactly
282    /// reverse the changes performed in [`Self::up_with_hook()`]. `hook` will run before the SQL
283    /// statement is executed.
284    pub fn down_with_hook(mut self, sql: &'u str, hook: impl MigrationHook + 'static) -> Self {
285        self.down = Some(sql);
286        self.down_hook = Some(hook.clone_box());
287        self
288    }
289
290    /// Enable an automatic validation of foreign keys before the migration transaction is closed.
291    /// This works both for upward and downward migrations.
292    ///
293    /// This will cause the migration to fail if [`PRAGMA foreign_key_check`][fkc] returns any
294    /// foreign key check violations.
295    ///
296    /// # Turning `PRAGMA foreign_keys` ON and OFF
297    ///
298    /// By default with SQLite, foreign key constraints are not checked (that [may change in the
299    /// future][fk]). If you wish to check this, you need to manually turn [`PRAGMA
300    /// foreign_keys`][fk] ON. However, the [documentation for “Making Other Kinds Of Table Schema
301    /// Changes”][doc_other_migration] suggests turning this OFF before running the migrations.
302    ///
303    /// This if you want to enforce foreign key checks, it seems best to disable it first (in case
304    /// future versions of SQLite enable it by default), then run the migrations, then enable it,
305    /// as in the example below.
306    ///
307    /// Please make sure you **do not** call `PRAGMA foreign_keys` from inside the migrations, as
308    /// it would be a no-op (each migration is run inside a transaction).
309    ///
310    /// # Example
311    ///
312    /// ```
313    /// use rusqlite::{params, Connection};
314    /// use rusqlite_migration::{Migrations, M};
315    ///
316    /// let migrations = Migrations::new(vec![
317    ///     M::up("CREATE TABLE animals (name TEXT);")
318    ///         .foreign_key_check(), // Let’s pretend this is necessary here
319    /// ]);
320    ///
321    /// let mut conn = Connection::open_in_memory().unwrap();
322    ///
323    /// // Turn foreign key constraints off for the duration of the migration
324    /// conn.pragma_update(None, "foreign_keys", &"OFF").unwrap();
325    ///
326    /// migrations.to_latest(&mut conn).unwrap();
327    ///
328    /// // Restore foreign key constraints checks
329    /// conn.pragma_update(None, "foreign_keys", &"ON").unwrap();
330    ///
331    /// conn.execute("INSERT INTO animals (name) VALUES (?1)", params!["dog"])
332    ///     .unwrap();
333    /// ```
334    ///
335    /// [fk]: https://www.sqlite.org/pragma.html#pragma_foreign_keys
336    /// [fkc]: https://www.sqlite.org/pragma.html#pragma_foreign_key_check
337    /// [doc_other_migration]: https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes
338    pub const fn foreign_key_check(mut self) -> Self {
339        self.foreign_key_check = true;
340        self
341    }
342}
343
344/// Schema version, in the context of Migrations
345#[derive(Debug, PartialEq, Eq, Clone, Copy)]
346pub enum SchemaVersion {
347    /// No schema version set
348    NoneSet,
349    /// The current version in the database is inside the range of defined
350    /// migrations
351    Inside(NonZeroUsize),
352    /// The current version in the database is outside any migration defined
353    Outside(NonZeroUsize),
354}
355
356impl From<&SchemaVersion> for usize {
357    /// Translate schema version to db version
358    fn from(schema_version: &SchemaVersion) -> usize {
359        match schema_version {
360            SchemaVersion::NoneSet => 0,
361            SchemaVersion::Inside(v) | SchemaVersion::Outside(v) => From::from(*v),
362        }
363    }
364}
365
366impl From<SchemaVersion> for usize {
367    fn from(schema_version: SchemaVersion) -> Self {
368        From::from(&schema_version)
369    }
370}
371
372impl fmt::Display for SchemaVersion {
373    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
374        match self {
375            SchemaVersion::NoneSet => write!(f, "0 (no version set)"),
376            SchemaVersion::Inside(v) => write!(f, "{v} (inside)"),
377            SchemaVersion::Outside(v) => write!(f, "{v} (outside)"),
378        }
379    }
380}
381
382impl cmp::PartialOrd for SchemaVersion {
383    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
384        let self_usize: usize = self.into();
385        let other_usize: usize = other.into();
386
387        self_usize.partial_cmp(&other_usize)
388    }
389}
390
391/// Set of migrations
392#[derive(Debug, PartialEq, Eq, Clone)]
393pub struct Migrations<'m> {
394    ms: Cow<'m, [M<'m>]>,
395}
396
397impl<'m> Migrations<'m> {
398    /// Create a set of migrations. See also [`Migrations::from_slice`], in particular to hold
399    /// migrations into a constant.
400    ///
401    /// # Example
402    ///
403    /// ```
404    /// use rusqlite_migration::{Migrations, M};
405    ///
406    /// let migrations = Migrations::new(vec![
407    ///     M::up("CREATE TABLE animals (name TEXT);"),
408    ///     M::up("CREATE TABLE food (name TEXT);"),
409    /// ]);
410    /// ```
411    #[must_use]
412    pub const fn new(ms: Vec<M<'m>>) -> Self {
413        Self { ms: Cow::Owned(ms) }
414    }
415
416    /// Similar to [`Migrations::new`], but accepts a slice instead. Especially useful in `const`
417    /// contexts, when the migrations are known at compile time.
418    ///
419    /// # Example
420    ///
421    /// ```
422    /// use rusqlite_migration::{Migrations, M};
423    ///
424    /// const MIGRATION_ARRAY: &[M] = &[
425    ///     M::up("CREATE TABLE animals (name TEXT);"),
426    ///     M::up("CREATE TABLE food (name TEXT);"),
427    /// ];
428    /// const MIGRATIONS: Migrations = Migrations::from_slice(MIGRATION_ARRAY);
429    /// ```
430    #[must_use]
431    pub const fn from_slice(ms: &'m [M<'m>]) -> Self {
432        Self {
433            ms: Cow::Borrowed(ms),
434        }
435    }
436
437    /// Creates a set of migrations from a given directory by scanning subdirectories with a specified name pattern.
438    /// The migrations are loaded and stored in the binary.
439    ///
440    /// # Directory Structure Requirements
441    ///
442    /// The migration directory pointed to by `include_dir!()` must contain
443    /// subdirectories in accordance with the given pattern:
444    /// `{usize id indicating the order}-{convenient migration name}`
445    ///
446    /// Those directories must contain at lest an `up.sql` file containing a valid upward
447    /// migration. They can also contain a `down.sql` file containing a downward migration.
448    ///
449    /// ## Example structure
450    ///
451    /// ```no_test
452    /// migrations
453    /// ├── 01-friend_car
454    /// │  └── up.sql
455    /// ├── 02-add_birthday_column
456    /// │  └── up.sql
457    /// └── 03-add_animal_table
458    ///    ├── down.sql
459    ///    └── up.sql
460    /// ```
461    ///
462    /// # Example
463    ///
464    /// ```
465    /// use rusqlite_migration::Migrations;
466    /// use include_dir::{Dir, include_dir};
467    ///
468    /// static MIGRATION_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/../examples/from-directory/migrations");
469    /// let migrations = Migrations::from_directory(&MIGRATION_DIR).unwrap();
470    /// ```
471    ///
472    /// # Errors
473    ///
474    /// Returns [`Error::FileLoad`] in case the subdirectory names are incorrect,
475    /// or don't contain at least a valid `up.sql` file.
476    #[cfg(feature = "from-directory")]
477    pub fn from_directory(dir: &'static Dir<'static>) -> Result<Self> {
478        let migrations = from_directory(dir)?
479            .into_iter()
480            .collect::<Option<Cow<_>>>()
481            .ok_or(Error::FileLoad("Could not load migrations".to_string()))?;
482
483        Ok(Self { ms: migrations })
484    }
485
486    fn db_version_to_schema(&self, db_version: usize) -> SchemaVersion {
487        match db_version {
488            0 => SchemaVersion::NoneSet,
489            v if v > 0 && v <= self.ms.len() => SchemaVersion::Inside(
490                NonZeroUsize::new(v).expect("schema version should not be equal to 0"),
491            ),
492            v => SchemaVersion::Outside(
493                NonZeroUsize::new(v).expect("schema version should not be equal to 0"),
494            ),
495        }
496    }
497
498    /// Get the current schema version
499    ///
500    /// # Example
501    ///
502    /// ```
503    /// use rusqlite_migration::{Migrations, M, SchemaVersion};
504    /// use std::num::NonZeroUsize;
505    ///
506    /// let mut conn = rusqlite::Connection::open_in_memory().unwrap();
507    ///
508    /// let migrations = Migrations::new(vec![
509    ///     M::up("CREATE TABLE animals (name TEXT);"),
510    ///     M::up("CREATE TABLE food (name TEXT);"),
511    /// ]);
512    ///
513    /// assert_eq!(SchemaVersion::NoneSet, migrations.current_version(&conn).unwrap());
514    ///
515    /// // Go to the latest version
516    /// migrations.to_latest(&mut conn).unwrap();
517    ///
518    /// assert_eq!(SchemaVersion::Inside(NonZeroUsize::new(2).unwrap()), migrations.current_version(&conn).unwrap());
519    /// ```
520    ///
521    /// # Errors
522    ///
523    /// Returns [`Error::RusqliteError`] or [`Error::InvalidUserVersion`] in case the user
524    /// version cannot be queried.
525    pub fn current_version(&self, conn: &Connection) -> Result<SchemaVersion> {
526        user_version(conn).map(|v| self.db_version_to_schema(v))
527    }
528
529    /// Returns the number of migrations that would be applied by [`Migrations::to_latest`]. For
530    /// instance, if one migration has not been applied yet, the number returned will be 1.
531    ///
532    /// The number returned may be negative. This happens when more migrations were applied than
533    /// the current version of the program knows about. It then represent the number of migrations
534    /// applied beyond that point. You can also see it as the number of migrations that would need
535    /// to be undone.
536    ///
537    /// <div class="warning">
538    ///
539    /// For most common scenarios, you should be able to just call [`Migrations::to_latest`], which
540    /// already checks the schema version.
541    ///
542    /// </div>
543    ///
544    /// # Examples
545    ///
546    /// ## Backup before applying migrations
547    ///
548    /// One common use case for this function is ta take a backup of the database before applying
549    /// migrations, if any migrations would run.
550    ///
551    /// ```
552    /// use rusqlite_migration::{Migrations, M};
553    ///
554    /// let mut conn = rusqlite::Connection::open_in_memory().unwrap();
555    /// let mut migrations: Migrations = Migrations::new(vec![
556    ///     M::up("CREATE TABLE animals (name TEXT);"),
557    ///     M::up("CREATE TABLE food (name TEXT);"),
558    /// ]);
559    ///
560    /// if migrations.pending_migrations(&conn).unwrap() != 0 {
561    ///     // Backup the database
562    ///
563    ///     migrations.to_latest(&mut conn).unwrap()
564    /// }
565    /// ```
566    ///
567    ///
568    /// ## Negative numbers
569    ///
570    /// This demonstrate how negative numbers are returned on a database modified by a newer
571    /// version of the program and then that same database is opened again by the older version.
572    ///
573    /// ```rust
574    /// use rusqlite_migration::{Error, Migrations, M, MigrationDefinitionError};
575    ///
576    /// let mut conn = rusqlite::Connection::open_in_memory().unwrap();
577    ///
578    /// // Initial set of migrations in, say, version 1 of the program
579    /// let mut ms = vec![
580    ///     M::up("CREATE TABLE animals (name TEXT);"),
581    ///     M::up("CREATE TABLE food (name TEXT);"),
582    /// ];
583    /// let migrations_v1 = Migrations::new(ms.clone());
584    ///
585    /// migrations_v1.to_latest(&mut conn).unwrap();
586    /// assert_eq!(migrations_v1.pending_migrations(&conn), Ok(0));
587    ///
588    /// // More migrations are added in, say, version 2
589    /// ms.push(M::up("CREATE TABLE plants (name TEXT);"));
590    /// let migrations_v2 =  Migrations::new(ms);
591    ///
592    /// migrations_v2.to_latest(&mut conn).unwrap();
593    /// // From the perspective of the old version of the program, one migration would need to be
594    /// // reversed.
595    /// assert_eq!(migrations_v1.pending_migrations(&conn), Ok(-1));
596    /// // Note that in this situation, to_latest will return an error, which you can handle how
597    /// // you see fit (maybe restoring one of those backups or prompting the user)
598    /// assert_eq!(migrations_v1.to_latest(&mut conn), Err(Error::MigrationDefinition(
599    ///     MigrationDefinitionError::DatabaseTooFarAhead
600    /// )));
601    /// ```
602    ///
603    /// # Errors
604    ///
605    /// Returns [`Error::RusqliteError`] or [`Error::InvalidUserVersion`] in case the user
606    /// version cannot be queried.
607    pub fn pending_migrations(&self, conn: &Connection) -> Result<i32> {
608        Ok(self.ms.len() as i32 - user_version(conn)? as i32)
609    }
610
611    /// Migrate upward methods. This is rolled back on error.
612    /// On success, returns the number of update performed
613    /// All versions are db versions
614    fn goto_up(
615        &self,
616        conn: &mut Connection,
617        current_version: usize,
618        target_version: usize,
619    ) -> Result<()> {
620        debug_assert!(current_version <= target_version);
621        debug_assert!(target_version <= self.ms.len());
622
623        trace!("start migration transaction");
624        let tx = conn.transaction()?;
625
626        for v in current_version..target_version {
627            let m = &self.ms[v];
628            debug!("Running: {}", m.up);
629
630            tx.execute_batch(m.up)
631                .map_err(|e| Error::with_sql(e, m.up))?;
632
633            if m.foreign_key_check {
634                validate_foreign_keys(&tx)?;
635            }
636
637            if let Some(hook) = &m.up_hook {
638                hook(&tx)?;
639            }
640        }
641
642        set_user_version(&tx, target_version)?;
643        tx.commit()?;
644        trace!("committed migration transaction");
645
646        Ok(())
647    }
648
649    /// Migrate downward. This is rolled back on error.
650    /// All versions are db versions
651    fn goto_down(
652        &self,
653        conn: &mut Connection,
654        current_version: usize,
655        target_version: usize,
656    ) -> Result<()> {
657        debug_assert!(current_version >= target_version);
658        debug_assert!(target_version <= self.ms.len());
659
660        // First, check if all the migrations have a "down" version
661        if let Some((i, bad_m)) = self
662            .ms
663            .iter()
664            .enumerate()
665            .skip(target_version)
666            .take(current_version - target_version)
667            .find(|(_, m)| m.down.is_none())
668        {
669            warn!("Cannot revert: {:?}", bad_m);
670            return Err(Error::MigrationDefinition(
671                MigrationDefinitionError::DownNotDefined { migration_index: i },
672            ));
673        }
674
675        trace!("start migration transaction");
676        let tx = conn.transaction()?;
677        for v in (target_version..current_version).rev() {
678            let m = &self.ms[v];
679            if let Some(down) = m.down {
680                debug!("Running: {}", &down);
681
682                if let Some(hook) = &m.down_hook {
683                    hook(&tx)?;
684                }
685
686                tx.execute_batch(down)
687                    .map_err(|e| Error::with_sql(e, down))?;
688
689                if m.foreign_key_check {
690                    validate_foreign_keys(&tx)?;
691                }
692            } else {
693                unreachable!();
694            }
695        }
696        set_user_version(&tx, target_version)?;
697        tx.commit()?;
698        trace!("committed migration transaction");
699        Ok(())
700    }
701
702    /// Go to a given db version
703    fn goto(&self, conn: &mut Connection, target_db_version: usize) -> Result<()> {
704        let current_version = user_version(conn)?;
705
706        let res = match target_db_version.cmp(&current_version) {
707            Ordering::Less => {
708                if current_version > self.ms.len() {
709                    return Err(Error::MigrationDefinition(
710                        MigrationDefinitionError::DatabaseTooFarAhead,
711                    ));
712                }
713                debug!(
714                    "rollback to older version requested, target_db_version: {}, current_version: {}",
715                    target_db_version, current_version
716                );
717                self.goto_down(conn, current_version, target_db_version)
718            }
719            Ordering::Equal => {
720                debug!("no migration to run, db already up to date");
721                return Ok(()); // return directly, so the migration message is not printed
722            }
723            Ordering::Greater => {
724                debug!(
725                    "some migrations to run, target: {target_db_version}, current: {current_version}"
726                );
727                self.goto_up(conn, current_version, target_db_version)
728            }
729        };
730
731        if res.is_ok() {
732            info!("Database migrated to version {}", target_db_version);
733        }
734        res
735    }
736
737    /// Maximum version defined in the migration set
738    fn max_schema_version(&self) -> SchemaVersion {
739        match self.ms.len() {
740            0 => SchemaVersion::NoneSet,
741            v => SchemaVersion::Inside(
742                NonZeroUsize::new(v).expect("schema version should not be equal to 0"),
743            ),
744        }
745    }
746
747    /// Migrate the database to latest schema version. The migrations are applied atomically.
748    ///
749    /// # Example
750    ///
751    /// ```
752    /// use rusqlite_migration::{Migrations, M};
753    /// let mut conn = rusqlite::Connection::open_in_memory().unwrap();
754    ///
755    /// let migrations = Migrations::new(vec![
756    ///     M::up("CREATE TABLE animals (name TEXT);"),
757    ///     M::up("CREATE TABLE food (name TEXT);"),
758    /// ]);
759    ///
760    /// // Go to the latest version
761    /// migrations.to_latest(&mut conn).unwrap();
762    ///
763    /// // You can then insert values in the database
764    /// conn.execute("INSERT INTO animals (name) VALUES (?)", ["dog"]).unwrap();
765    /// conn.execute("INSERT INTO food (name) VALUES (?)", ["carrot"]).unwrap();
766    /// ```
767    ///
768    /// # Errors
769    ///
770    /// Returns [`Error::MigrationDefinition`] if no migration is defined.
771    ///
772    /// Returns [`Error::RusqliteError`] if rusqlite returns an error when executing a migration
773    /// statement. Note that this immediatley stops applying migrations.
774    /// ```rust
775    /// # use rusqlite_migration::{Migrations, M};
776    /// let mut conn = rusqlite::Connection::open_in_memory().unwrap();
777    ///
778    /// let migrations = Migrations::new(vec![
779    ///     M::up("CREATE TABLE t1 (c);"),
780    ///     M::up("SYNTAX ERROR"),         // This won’t be applied
781    ///     M::up("CREATE TABLE t2 (c);"), // This won’t be applied either because the migration above
782    ///                                    // failed
783    /// ]);
784    ///
785    /// assert!(matches!(
786    ///     migrations.to_latest(&mut conn),
787    ///     Err(rusqlite_migration::Error::RusqliteError {
788    ///         query: _,
789    ///         err: rusqlite::Error::SqliteFailure(_, _),
790    ///     })
791    /// ));
792    /// ```
793    /// If rusqlite `extra_check` feature is enabled, any migration that returns a value will error
794    /// and no further migrations will be applied.
795    ///
796    /// # Transaction Behavior
797    ///
798    /// Since rusqlite 0.33, a [default transaction behavior][default_behavior] can be set. For
799    /// now, when applying migrations, this setting will be respected.
800    ///
801    /// Please note that future minor versions of rusqlite_migration might decide to ignore the
802    /// setting and to instead use any transaction behavior deemed most appropriate.  You can read
803    /// more in the [corresponding page of the SQLite documentation][sqlite_doc].
804    ///
805    ///
806    /// [default_behavior]: https://github.com/rusqlite/rusqlite/pull/1532
807    /// [sqlite_doc]: https://sqlite.org/lang_transaction.html
808    pub fn to_latest(&self, conn: &mut Connection) -> Result<()> {
809        let v_max = self.max_schema_version();
810        match v_max {
811            SchemaVersion::NoneSet => {
812                warn!("no migration defined");
813                Err(Error::MigrationDefinition(
814                    MigrationDefinitionError::NoMigrationsDefined,
815                ))
816            }
817            SchemaVersion::Inside(v) => {
818                debug!("some migrations defined (version: {v}), try to migrate");
819                self.goto(conn, v_max.into())
820            }
821            SchemaVersion::Outside(_) => unreachable!(),
822        }
823    }
824
825    /// Migrate the database to a given schema version. The migrations are applied atomically.
826    ///
827    /// # Specifying versions
828    ///
829    /// - Empty database (no migrations run yet) has version `0`.
830    /// - The version increases after each migration, so after the first migration has run, the schema version is `1`. For instance, if there are 3 migrations, version `3` is after all migrations have run.
831    ///
832    /// *Note*: As a result, the version is the index in the migrations vector *starting from 1*.
833    ///
834    /// # Example
835    ///
836    /// ```
837    /// use rusqlite_migration::{Migrations, M};
838    /// let mut conn = rusqlite::Connection::open_in_memory().unwrap();
839    /// let migrations = Migrations::new(vec![
840    ///     // 0: version 0, before having run any migration
841    ///     M::up("CREATE TABLE animals (name TEXT);").down("DROP TABLE animals;"),
842    ///     // 1: version 1, after having created the “animals” table
843    ///     M::up("CREATE TABLE food (name TEXT);").down("DROP TABLE food;"),
844    ///     // 2: version 2, after having created the food table
845    /// ]);
846    ///
847    /// migrations.to_latest(&mut conn).unwrap(); // Create all tables
848    ///
849    /// // Go back to version 1, i.e. after running the first migration
850    /// migrations.to_version(&mut conn, 1);
851    /// conn.execute("INSERT INTO animals (name) VALUES (?)", ["dog"]).unwrap();
852    /// conn.execute("INSERT INTO food (name) VALUES (?)", ["carrot"]).unwrap_err();
853    ///
854    /// // Go back to an empty database
855    /// migrations.to_version(&mut conn, 0);
856    /// conn.execute("INSERT INTO animals (name) VALUES (?)", ["cat"]).unwrap_err();
857    /// conn.execute("INSERT INTO food (name) VALUES (?)", ["milk"]).unwrap_err();
858    /// ```
859    ///
860    /// # Errors
861    ///
862    /// Attempts to migrate to a higher version than is supported will result in an error.
863    ///
864    /// When migrating downwards, all the reversed migrations must have a `.down()` variant,
865    /// otherwise no migrations are run and the function returns an error.
866    pub fn to_version(&self, conn: &mut Connection, version: usize) -> Result<()> {
867        let target_version: SchemaVersion = self.db_version_to_schema(version);
868        let v_max = self.max_schema_version();
869        match v_max {
870            SchemaVersion::NoneSet => {
871                warn!("no migrations defined");
872                Err(Error::MigrationDefinition(
873                    MigrationDefinitionError::NoMigrationsDefined,
874                ))
875            }
876            SchemaVersion::Inside(v) => {
877                debug!("some migrations defined (version: {v}), try to migrate");
878                if target_version > v_max {
879                    warn!("specified version is higher than the max supported version");
880                    return Err(Error::SpecifiedSchemaVersion(
881                        SchemaVersionError::TargetVersionOutOfRange {
882                            specified: target_version,
883                            highest: v_max,
884                        },
885                    ));
886                }
887
888                self.goto(conn, target_version.into())
889            }
890            SchemaVersion::Outside(_) => unreachable!(
891                "max_schema_version should not return SchemaVersion::Outside.
892                This is a bug, please report it."
893            ),
894        }
895    }
896
897    /// Run upward migrations on a temporary in-memory database from first to last, one by one.
898    /// Convenience method for testing.
899    ///
900    /// # Example
901    ///
902    /// ```
903    /// #[cfg(test)]
904    /// mod tests {
905    ///
906    ///     // … Other tests …
907    ///
908    ///     #[test]
909    ///     fn migrations_test() {
910    ///         migrations.validate().unwrap();
911    ///     }
912    /// }
913    /// ```
914    ///
915    /// # Errors
916    ///
917    /// Returns [`Error::RusqliteError`] if the underlying sqlite database open call fails.
918    pub fn validate(&self) -> Result<()> {
919        let mut conn = Connection::open_in_memory()?;
920        self.to_latest(&mut conn)
921    }
922}
923
924// Read user version field from the SQLite db
925fn user_version(conn: &Connection) -> Result<usize> {
926    // We can’t fix this without breaking API compatibility
927    conn.query_row("PRAGMA user_version", [], |row| row.get(0))
928        .map_err(|e| Error::RusqliteError {
929            query: String::from("PRAGMA user_version;"),
930            err: e,
931        })
932        .and_then(|v: i32| {
933            if v >= 0 {
934                Ok(v as usize)
935            } else {
936                Err(Error::InvalidUserVersion)
937            }
938        })
939}
940
941// Set user version field from the SQLite db
942fn set_user_version(conn: &Connection, v: usize) -> Result<()> {
943    trace!("set user version to: {}", v);
944    let v = if v > MIGRATIONS_MAX {
945        Err(Error::SpecifiedSchemaVersion(SchemaVersionError::TooHigh))
946    } else {
947        Ok(i32::try_from(v).unwrap_or_else(|e| {
948            unreachable!(
949                "Value {v} was checked to be convertible to a i32, but error {e} occured.\n\
950                This is a bug, please report it."
951            )
952        }))
953    }?;
954    conn.pragma_update(None, "user_version", v)
955        .map_err(|e| Error::RusqliteError {
956            query: format!("PRAGMA user_version = {v}; -- Approximate query"),
957            err: e,
958        })
959}
960
961// Validate that no foreign keys are violated
962fn validate_foreign_keys(conn: &Connection) -> Result<()> {
963    let pragma_fk_check = "PRAGMA foreign_key_check";
964    let mut stmt = conn
965        .prepare_cached(pragma_fk_check)
966        .map_err(|e| Error::with_sql(e, pragma_fk_check))?;
967
968    let fk_errors = stmt
969        .query_map([], |row| {
970            Ok(ForeignKeyCheckError {
971                table: row.get(0)?,
972                rowid: row.get(1)?,
973                parent: row.get(2)?,
974                fkid: row.get(3)?,
975            })
976        })
977        .map_err(|e| Error::with_sql(e, pragma_fk_check))?
978        .collect::<Result<Vec<_>, _>>()?;
979    if !fk_errors.is_empty() {
980        Err(crate::Error::ForeignKeyCheck(fk_errors))
981    } else {
982        Ok(())
983    }
984}
985
986impl<'u> FromIterator<M<'u>> for Migrations<'u> {
987    fn from_iter<T: IntoIterator<Item = M<'u>>>(iter: T) -> Self {
988        Self {
989            ms: Cow::Owned(Vec::from_iter(iter)),
990        }
991    }
992}