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 <= 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(¤t_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: {target_db_version}, current_version: {current_version}",
715 );
716 self.goto_down(conn, current_version, target_db_version)
717 }
718 Ordering::Equal => {
719 debug!("no migration to run, db already up to date");
720 return Ok(()); // return directly, so the migration message is not printed
721 }
722 Ordering::Greater => {
723 debug!(
724 "some migrations to run, target: {target_db_version}, current: {current_version}"
725 );
726 self.goto_up(conn, current_version, target_db_version)
727 }
728 };
729
730 if res.is_ok() {
731 info!("Database migrated to version {target_db_version}");
732 }
733 res
734 }
735
736 /// Maximum version defined in the migration set
737 fn max_schema_version(&self) -> SchemaVersion {
738 match self.ms.len() {
739 0 => SchemaVersion::NoneSet,
740 v => SchemaVersion::Inside(
741 NonZeroUsize::new(v).expect("schema version should not be equal to 0"),
742 ),
743 }
744 }
745
746 /// Migrate the database to latest schema version. The migrations are applied atomically.
747 ///
748 /// # Example
749 ///
750 /// ```
751 /// use rusqlite_migration::{Migrations, M};
752 /// let mut conn = rusqlite::Connection::open_in_memory().unwrap();
753 ///
754 /// let migrations = Migrations::new(vec![
755 /// M::up("CREATE TABLE animals (name TEXT);"),
756 /// M::up("CREATE TABLE food (name TEXT);"),
757 /// ]);
758 ///
759 /// // Go to the latest version
760 /// migrations.to_latest(&mut conn).unwrap();
761 ///
762 /// // You can then insert values in the database
763 /// conn.execute("INSERT INTO animals (name) VALUES (?)", ["dog"]).unwrap();
764 /// conn.execute("INSERT INTO food (name) VALUES (?)", ["carrot"]).unwrap();
765 /// ```
766 ///
767 /// # Errors
768 ///
769 /// Returns [`Error::MigrationDefinition`] if no migration is defined.
770 ///
771 /// Returns [`Error::RusqliteError`] if rusqlite returns an error when executing a migration
772 /// statement. Note that this immediatley stops applying migrations.
773 /// ```rust
774 /// # use rusqlite_migration::{Migrations, M};
775 /// let mut conn = rusqlite::Connection::open_in_memory().unwrap();
776 ///
777 /// let migrations = Migrations::new(vec![
778 /// M::up("CREATE TABLE t1 (c);"),
779 /// M::up("SYNTAX ERROR"), // This won’t be applied
780 /// M::up("CREATE TABLE t2 (c);"), // This won’t be applied either because the migration above
781 /// // failed
782 /// ]);
783 ///
784 /// assert!(matches!(
785 /// migrations.to_latest(&mut conn),
786 /// Err(rusqlite_migration::Error::RusqliteError {
787 /// query: _,
788 /// err: rusqlite::Error::SqliteFailure(_, _),
789 /// })
790 /// ));
791 /// ```
792 /// If rusqlite `extra_check` feature is enabled, any migration that returns a value will error
793 /// and no further migrations will be applied.
794 ///
795 /// # Transaction Behavior
796 ///
797 /// Since rusqlite 0.33, a [default transaction behavior][default_behavior] can be set. For
798 /// now, when applying migrations, this setting will be respected.
799 ///
800 /// Please note that future minor versions of rusqlite_migration might decide to ignore the
801 /// setting and to instead use any transaction behavior deemed most appropriate. You can read
802 /// more in the [corresponding page of the SQLite documentation][sqlite_doc].
803 ///
804 ///
805 /// [default_behavior]: https://github.com/rusqlite/rusqlite/pull/1532
806 /// [sqlite_doc]: https://sqlite.org/lang_transaction.html
807 pub fn to_latest(&self, conn: &mut Connection) -> Result<()> {
808 let v_max = self.max_schema_version();
809 match v_max {
810 SchemaVersion::NoneSet => {
811 warn!("no migration defined");
812 Err(Error::MigrationDefinition(
813 MigrationDefinitionError::NoMigrationsDefined,
814 ))
815 }
816 SchemaVersion::Inside(v) => {
817 debug!("some migrations defined (version: {v}), try to migrate");
818 self.goto(conn, v_max.into())
819 }
820 SchemaVersion::Outside(_) => unreachable!(),
821 }
822 }
823
824 /// Migrate the database to a given schema version. The migrations are applied atomically.
825 ///
826 /// # Specifying versions
827 ///
828 /// - Empty database (no migrations run yet) has version `0`.
829 /// - 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.
830 ///
831 /// *Note*: As a result, the version is the index in the migrations vector *starting from 1*.
832 ///
833 /// # Example
834 ///
835 /// ```
836 /// use rusqlite_migration::{Migrations, M};
837 /// let mut conn = rusqlite::Connection::open_in_memory().unwrap();
838 /// let migrations = Migrations::new(vec![
839 /// // 0: version 0, before having run any migration
840 /// M::up("CREATE TABLE animals (name TEXT);").down("DROP TABLE animals;"),
841 /// // 1: version 1, after having created the “animals” table
842 /// M::up("CREATE TABLE food (name TEXT);").down("DROP TABLE food;"),
843 /// // 2: version 2, after having created the food table
844 /// ]);
845 ///
846 /// migrations.to_latest(&mut conn).unwrap(); // Create all tables
847 ///
848 /// // Go back to version 1, i.e. after running the first migration
849 /// migrations.to_version(&mut conn, 1);
850 /// conn.execute("INSERT INTO animals (name) VALUES (?)", ["dog"]).unwrap();
851 /// conn.execute("INSERT INTO food (name) VALUES (?)", ["carrot"]).unwrap_err();
852 ///
853 /// // Go back to an empty database
854 /// migrations.to_version(&mut conn, 0);
855 /// conn.execute("INSERT INTO animals (name) VALUES (?)", ["cat"]).unwrap_err();
856 /// conn.execute("INSERT INTO food (name) VALUES (?)", ["milk"]).unwrap_err();
857 /// ```
858 ///
859 /// # Errors
860 ///
861 /// Attempts to migrate to a higher version than is supported will result in an error.
862 ///
863 /// When migrating downwards, all the reversed migrations must have a `.down()` variant,
864 /// otherwise no migrations are run and the function returns an error.
865 pub fn to_version(&self, conn: &mut Connection, version: usize) -> Result<()> {
866 let target_version: SchemaVersion = self.db_version_to_schema(version);
867 let v_max = self.max_schema_version();
868 match v_max {
869 SchemaVersion::NoneSet => {
870 warn!("no migrations defined");
871 Err(Error::MigrationDefinition(
872 MigrationDefinitionError::NoMigrationsDefined,
873 ))
874 }
875 SchemaVersion::Inside(v) => {
876 debug!("some migrations defined (version: {v}), try to migrate");
877 if target_version > v_max {
878 warn!("specified version is higher than the max supported version");
879 return Err(Error::SpecifiedSchemaVersion(
880 SchemaVersionError::TargetVersionOutOfRange {
881 specified: target_version,
882 highest: v_max,
883 },
884 ));
885 }
886
887 self.goto(conn, target_version.into())
888 }
889 SchemaVersion::Outside(_) => unreachable!(
890 "max_schema_version should not return SchemaVersion::Outside.
891 This is a bug, please report it."
892 ),
893 }
894 }
895
896 /// Run upward migrations on a temporary in-memory database from first to last, one by one.
897 /// Convenience method for testing.
898 ///
899 /// # Example
900 ///
901 /// ```
902 /// #[cfg(test)]
903 /// mod tests {
904 ///
905 /// // … Other tests …
906 ///
907 /// #[test]
908 /// fn migrations_test() {
909 /// migrations.validate().unwrap();
910 /// }
911 /// }
912 /// ```
913 ///
914 /// # Errors
915 ///
916 /// Returns [`Error::RusqliteError`] if the underlying sqlite database open call fails.
917 pub fn validate(&self) -> Result<()> {
918 let mut conn = Connection::open_in_memory()?;
919 self.to_latest(&mut conn)
920 }
921}
922
923// Read user version field from the SQLite db
924fn user_version(conn: &Connection) -> Result<usize> {
925 // We can’t fix this without breaking API compatibility
926 conn.query_row("PRAGMA user_version", [], |row| row.get(0))
927 .map_err(|e| Error::RusqliteError {
928 query: String::from("PRAGMA user_version;"),
929 err: e,
930 })
931 .and_then(|v: i32| {
932 if v >= 0 {
933 Ok(v as usize)
934 } else {
935 Err(Error::InvalidUserVersion)
936 }
937 })
938}
939
940// Set user version field from the SQLite db
941fn set_user_version(conn: &Connection, v: usize) -> Result<()> {
942 trace!("set user version to: {v}");
943 let v = if v > MIGRATIONS_MAX {
944 Err(Error::SpecifiedSchemaVersion(SchemaVersionError::TooHigh))
945 } else {
946 Ok(i32::try_from(v).unwrap_or_else(|e| {
947 unreachable!(
948 "Value {v} was checked to be convertible to a i32, but error {e} occured.\n\
949 This is a bug, please report it."
950 )
951 }))
952 }?;
953 conn.pragma_update(None, "user_version", v)
954 .map_err(|e| Error::RusqliteError {
955 query: format!("PRAGMA user_version = {v}; -- Approximate query"),
956 err: e,
957 })
958}
959
960// Validate that no foreign keys are violated
961fn validate_foreign_keys(conn: &Connection) -> Result<()> {
962 let pragma_fk_check = "PRAGMA foreign_key_check";
963 let mut stmt = conn
964 .prepare_cached(pragma_fk_check)
965 .map_err(|e| Error::with_sql(e, pragma_fk_check))?;
966
967 let fk_errors = stmt
968 .query_map([], |row| {
969 Ok(ForeignKeyCheckError {
970 table: row.get(0)?,
971 rowid: row.get(1)?,
972 parent: row.get(2)?,
973 fkid: row.get(3)?,
974 })
975 })
976 .map_err(|e| Error::with_sql(e, pragma_fk_check))?
977 .collect::<Result<Vec<_>, _>>()?;
978 if !fk_errors.is_empty() {
979 Err(crate::Error::ForeignKeyCheck(fk_errors))
980 } else {
981 Ok(())
982 }
983}
984
985impl<'u> FromIterator<M<'u>> for Migrations<'u> {
986 fn from_iter<T: IntoIterator<Item = M<'u>>>(iter: T) -> Self {
987 Self {
988 ms: Cow::Owned(Vec::from_iter(iter)),
989 }
990 }
991}