1use std::sync::Arc;
30
31use rainier_orm::{Blueprint, Dialect, Entity, Row, TableChanges};
32use rainier_support::{Error, Result};
33
34use crate::connection::Database;
35
36#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum Down {
44 Statements(Vec<String>),
46 Irreversible(String),
52}
53
54impl Down {
55 pub fn statements(statements: impl IntoIterator<Item = String>) -> Self {
57 Self::Statements(statements.into_iter().collect())
58 }
59
60 pub fn irreversible(reason: impl Into<String>) -> Self {
62 Self::Irreversible(reason.into())
63 }
64
65 pub fn sql(&self, migration: &str) -> Result<&[String]> {
67 match self {
68 Down::Statements(statements) => Ok(statements),
69 Down::Irreversible(reason) => {
70 Err(Error::internal(format!("`{migration}` cannot be rolled back: {reason}")))
71 }
72 }
73 }
74
75 pub fn is_reversible(&self) -> bool {
77 matches!(self, Down::Statements(_))
78 }
79}
80
81pub trait Migration: Send + Sync + 'static {
111 fn name(&self) -> &str;
115
116 fn up(&self, dialect: Dialect) -> Vec<String>;
121
122 fn down(&self, dialect: Dialect) -> Down;
124}
125
126pub struct Step {
131 name: String,
132 up: Box<dyn Fn(Dialect) -> Vec<String> + Send + Sync>,
133 down: Box<dyn Fn(Dialect) -> Down + Send + Sync>,
134}
135
136impl Step {
137 pub fn new(
139 name: impl Into<String>,
140 up: impl Fn(Dialect) -> Vec<String> + Send + Sync + 'static,
141 down: impl Fn(Dialect) -> Down + Send + Sync + 'static,
142 ) -> Self {
143 Self { name: name.into(), up: Box::new(up), down: Box::new(down) }
144 }
145
146 pub fn raw(name: impl Into<String>, up: Vec<String>, down: Vec<String>) -> Self {
148 Self::new(name, move |_| up.clone(), move |_| Down::Statements(down.clone()))
149 }
150
151 pub fn raw_irreversible(
153 name: impl Into<String>,
154 up: Vec<String>,
155 reason: impl Into<String>,
156 ) -> Self {
157 let reason = reason.into();
158 Self::new(name, move |_| up.clone(), move |_| Down::irreversible(reason.clone()))
159 }
160
161 pub fn create_table<E: Entity>(name: impl Into<String>) -> Self {
167 Self::new(
168 name,
169 |dialect| rainier_orm::schema::schema_ddl::<E>(dialect),
170 |_| Down::Statements(vec![format!("DROP TABLE IF EXISTS {}", E::table())]),
171 )
172 }
173
174 pub fn create(
193 name: impl Into<String>,
194 table: impl Into<String>,
195 build: impl FnOnce(&mut Blueprint),
196 ) -> Self {
197 let blueprint = Arc::new(Blueprint::create(table, build));
198 let reverse = Arc::clone(&blueprint);
199
200 Self::new(
201 name,
202 move |dialect| blueprint.to_sql(dialect),
203 move |dialect| Down::Statements(reverse.to_reverse_sql(dialect)),
204 )
205 }
206
207 pub fn table(
222 name: impl Into<String>,
223 table: impl Into<String>,
224 build: impl FnOnce(&mut TableChanges),
225 ) -> Self {
226 let changes = Arc::new(TableChanges::to(table, build));
227 let reverse = Arc::clone(&changes);
228
229 Self::new(
230 name,
231 move |dialect| changes.to_sql(dialect),
232 move |dialect| match reverse.irreversible_because() {
233 Some(reason) => Down::irreversible(reason),
234 None => Down::Statements(reverse.to_reverse_sql(dialect)),
235 },
236 )
237 }
238
239 pub fn drop_table(name: impl Into<String>, table: impl Into<String>) -> Self {
245 let table = table.into();
246 let reason = format!("`{table}` was dropped, along with everything in it");
247
248 Self::new(
249 name,
250 move |dialect| {
251 let mut stmt = rainier_orm::sea_query::Table::drop();
252 stmt.table(rainier_orm::sea_query::Alias::new(table.clone())).if_exists();
253 vec![dialect.build_schema(&stmt)]
254 },
255 move |_| Down::irreversible(reason.clone()),
256 )
257 }
258
259 pub fn rename_table(
264 name: impl Into<String>,
265 from: impl Into<String>,
266 to: impl Into<String>,
267 ) -> Self {
268 let (from, to) = (from.into(), to.into());
269 let (back_from, back_to) = (to.clone(), from.clone());
270
271 Self::new(
272 name,
273 move |dialect| vec![rename_table_sql(dialect, &from, &to)],
274 move |dialect| Down::Statements(vec![rename_table_sql(dialect, &back_from, &back_to)]),
275 )
276 }
277}
278
279fn rename_table_sql(dialect: Dialect, from: &str, to: &str) -> String {
281 let mut stmt = rainier_orm::sea_query::Table::rename();
282 stmt.table(
283 rainier_orm::sea_query::Alias::new(from.to_string()),
284 rainier_orm::sea_query::Alias::new(to.to_string()),
285 );
286 dialect.build_schema(&stmt)
287}
288
289impl Migration for Step {
290 fn name(&self) -> &str {
291 &self.name
292 }
293
294 fn up(&self, dialect: Dialect) -> Vec<String> {
295 (self.up)(dialect)
296 }
297
298 fn down(&self, dialect: Dialect) -> Down {
299 (self.down)(dialect)
300 }
301}
302
303impl std::fmt::Debug for Step {
304 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305 f.debug_struct("Step").field("name", &self.name).finish()
306 }
307}
308
309const LEDGER: &str = "rainier_migrations";
311
312#[derive(Default)]
314pub struct Migrator {
315 migrations: Vec<Arc<dyn Migration>>,
316}
317
318impl Migrator {
319 pub fn new() -> Self {
321 Self::default()
322 }
323
324 #[allow(clippy::should_implement_trait, reason = "a builder step, not arithmetic")]
326 pub fn add(mut self, migration: impl Migration) -> Self {
327 self.migrations.push(Arc::new(migration));
328 self
329 }
330
331 pub fn add_arc(mut self, migration: Arc<dyn Migration>) -> Self {
333 self.migrations.push(migration);
334 self
335 }
336
337 pub fn create_table<E: Entity>(self, name: impl Into<String>) -> Self {
339 self.add(Step::create_table::<E>(name))
340 }
341
342 pub fn step(
344 self,
345 name: impl Into<String>,
346 up: impl Fn(Dialect) -> Vec<String> + Send + Sync + 'static,
347 down: impl Fn(Dialect) -> Down + Send + Sync + 'static,
348 ) -> Self {
349 self.add(Step::new(name, up, down))
350 }
351
352 pub fn raw(self, name: impl Into<String>, up: Vec<String>, down: Vec<String>) -> Self {
354 self.add(Step::raw(name, up, down))
355 }
356
357 pub fn raw_irreversible(
359 self,
360 name: impl Into<String>,
361 up: Vec<String>,
362 reason: impl Into<String>,
363 ) -> Self {
364 self.add(Step::raw_irreversible(name, up, reason))
365 }
366
367 pub fn merge(mut self, other: Migrator) -> Self {
374 self.migrations.extend(other.migrations);
375 self
376 }
377
378 pub fn names(&self) -> Vec<&str> {
380 self.migrations.iter().map(|m| m.name()).collect()
381 }
382
383 pub fn len(&self) -> usize {
385 self.migrations.len()
386 }
387
388 pub fn is_empty(&self) -> bool {
390 self.migrations.is_empty()
391 }
392
393 pub fn irreversible(&self, dialect: Dialect) -> Vec<&str> {
398 self.migrations
399 .iter()
400 .filter(|m| !m.down(dialect).is_reversible())
401 .map(|m| m.name())
402 .collect()
403 }
404
405 pub async fn run(&self, db: &Database) -> Result<Vec<String>> {
416 self.ensure_ledger(db).await?;
417 let ledger = self.ledger_rows(db).await?;
418 let batch = ledger.iter().map(|(_, batch)| *batch).max().unwrap_or(0) + 1;
419
420 let mut ran = Vec::new();
421 for migration in &self.migrations {
422 let name = migration.name();
423 if ledger.iter().any(|(applied, _)| applied == name) {
424 continue;
425 }
426
427 for sql in migration.up(db.dialect()) {
428 self.execute(db, name, &sql, "migration").await?;
429 }
430
431 self.record(db, name, batch).await?;
432 tracing::info!(migration = %name, batch, "migrated");
433 ran.push(name.to_string());
434 }
435
436 Ok(ran)
437 }
438
439 pub async fn rollback(&self, db: &Database, batches: u32) -> Result<Vec<String>> {
453 let targets = self.rollback_targets(db, batches).await?;
454 if targets.is_empty() {
455 return Ok(Vec::new());
456 }
457
458 for name in &targets {
459 self.find(name)?.down(db.dialect()).sql(name)?;
460 }
461
462 let mut rolled_back = Vec::new();
463 for name in &targets {
464 let down = self.find(name)?.down(db.dialect());
465 for sql in down.sql(name)? {
466 self.execute(db, name, sql, "rollback").await?;
467 }
468
469 self.forget(db, name).await?;
470 tracing::info!(migration = %name, "rolled back");
471 rolled_back.push(name.clone());
472 }
473
474 Ok(rolled_back)
475 }
476
477 pub async fn rollback_targets(&self, db: &Database, batches: u32) -> Result<Vec<String>> {
482 if batches == 0 {
483 return Ok(Vec::new());
484 }
485
486 let mut rows = self.ledger_rows(db).await?;
487 if rows.is_empty() {
488 return Ok(Vec::new());
489 }
490
491 let mut numbers: Vec<i64> = rows.iter().map(|(_, batch)| *batch).collect();
495 numbers.sort_unstable();
496 numbers.dedup();
497 let cutoff = numbers.iter().rev().take(batches as usize).copied().min().unwrap_or(i64::MAX);
498 rows.retain(|(_, batch)| *batch >= cutoff);
499
500 let declared = self.names();
505 rows.sort_by_key(|(name, batch)| {
506 let position = declared.iter().position(|n| n == name).unwrap_or(usize::MAX);
507 (std::cmp::Reverse(*batch), std::cmp::Reverse(position))
508 });
509
510 Ok(rows.into_iter().map(|(name, _)| name).collect())
511 }
512
513 pub async fn applied(&self, db: &Database) -> Result<Vec<String>> {
515 Ok(self.ledger_rows(db).await?.into_iter().map(|(name, _)| name).collect())
516 }
517
518 pub async fn pending(&self, db: &Database) -> Result<Vec<&str>> {
520 let applied = self.applied(db).await?;
521 Ok(self.names().into_iter().filter(|name| !applied.iter().any(|a| a == name)).collect())
522 }
523
524 fn find(&self, name: &str) -> Result<&Arc<dyn Migration>> {
525 self.migrations.iter().find(|m| m.name() == name).ok_or_else(|| {
526 Error::internal(format!(
527 "`{name}` is in the ledger but not in this migrator, so there is nothing to \
528 run to undo it. It was probably deleted from the code while still applied."
529 ))
530 })
531 }
532
533 async fn execute(&self, db: &Database, name: &str, sql: &str, what: &str) -> Result<()> {
534 db.statement(sql).await.map(|_| ()).map_err(|e| {
535 Error::internal(format!(
536 "{what} `{name}` failed on `{}`: {e}",
537 rainier_support::str::limit(sql, 80, "…")
538 ))
539 })
540 }
541
542 async fn ledger_rows(&self, db: &Database) -> Result<Vec<(String, i64)>> {
544 self.ensure_ledger(db).await?;
545
546 let prepared = crate::statement::Prepared {
547 sql: format!("SELECT name, batch FROM {LEDGER}"),
548 params: Vec::new(),
549 route: rainier_orm::ShardRoute::Global,
550 };
551 let columns = vec![
552 crate::row::ColumnRequest::new("name", rainier_orm::ColumnType::Text),
553 crate::row::ColumnRequest::new("batch", rainier_orm::ColumnType::BigInt),
554 ];
555
556 let rows = db.fetch(prepared, columns).await?;
557 rows.iter()
558 .map(|row| {
559 let name = row
560 .get_string("name")
561 .map_err(Error::from)?
562 .ok_or_else(|| Error::internal("a migration ledger row had a NULL name"))?;
563 let batch = row.get_i64("batch").map_err(Error::from)?.unwrap_or(1);
566 Ok((name, batch))
567 })
568 .collect()
569 }
570
571 async fn ensure_ledger(&self, db: &Database) -> Result<()> {
572 db.statement(&format!(
575 "CREATE TABLE IF NOT EXISTS {LEDGER} (\
576 name VARCHAR(255) NOT NULL PRIMARY KEY, \
577 batch BIGINT NOT NULL DEFAULT 1)"
578 ))
579 .await?;
580 Ok(())
581 }
582
583 async fn record(&self, db: &Database, name: &str, batch: i64) -> Result<()> {
584 let values = if db.dialect() == Dialect::Postgres { "($1, $2)" } else { "(?, ?)" };
585 let prepared = crate::statement::Prepared {
586 sql: format!("INSERT INTO {LEDGER} (name, batch) VALUES {values}"),
587 params: vec![name.into(), batch.into()],
588 route: rainier_orm::ShardRoute::Global,
589 };
590
591 db.execute(prepared).await?;
592 Ok(())
593 }
594
595 async fn forget(&self, db: &Database, name: &str) -> Result<()> {
596 let placeholder = if db.dialect() == Dialect::Postgres { "$1" } else { "?" };
597 let prepared = crate::statement::Prepared {
598 sql: format!("DELETE FROM {LEDGER} WHERE name = {placeholder}"),
599 params: vec![name.into()],
600 route: rainier_orm::ShardRoute::Global,
601 };
602
603 db.execute(prepared).await?;
604 Ok(())
605 }
606}
607
608impl std::fmt::Debug for Migrator {
609 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
610 f.debug_struct("Migrator").field("migrations", &self.names()).finish()
611 }
612}
613
614#[cfg(test)]
615mod tests {
616 use super::*;
617 use crate::row::OwnedRow;
618 use crate::testing::{fake_database, MemoryConnection};
619
620 #[derive(rainier_orm::Entity, Clone, Debug)]
621 #[orm(table = "users")]
622 struct User {
623 #[orm(pk, auto_increment)]
624 id: u64,
625 #[orm(unique)]
626 email: String,
627 }
628
629 fn ledger(name: &str, batch: i64) -> OwnedRow {
631 OwnedRow::new().with("name", name).with("batch", batch)
632 }
633
634 #[test]
635 fn a_migrator_run_is_send() {
636 fn assert_send<T: Send>(_: T) {}
639
640 let (db, _) = fake_database(MemoryConnection::new(Dialect::Sqlite));
641 let migrator = Migrator::new().create_table::<User>("0001_users");
642 assert_send(async move { migrator.run(&db).await });
643 }
644
645 #[test]
646 fn a_rollback_is_send_too() {
647 fn assert_send<T: Send>(_: T) {}
648
649 let (db, _) = fake_database(MemoryConnection::new(Dialect::Sqlite));
650 let migrator = Migrator::new().create_table::<User>("0001_users");
651 assert_send(async move { migrator.rollback(&db, 1).await });
652 }
653
654 #[test]
655 fn create_table_renders_the_entitys_ddl() {
656 let step = Step::create_table::<User>("0001_users");
657 let statements = step.up(Dialect::Sqlite);
658
659 assert_eq!(step.name(), "0001_users");
660 assert!(statements[0].contains("users"), "{:?}", statements);
661 assert!(statements[0].contains("IF NOT EXISTS"), "{:?}", statements);
662 }
663
664 #[test]
665 fn create_table_undoes_itself_by_dropping_the_table() {
666 let step = Step::create_table::<User>("0001_users");
667
668 assert_eq!(
669 step.down(Dialect::Sqlite),
670 Down::Statements(vec!["DROP TABLE IF EXISTS users".to_string()])
671 );
672 }
673
674 #[test]
675 fn ddl_differs_per_dialect() {
676 let step = Step::create_table::<User>("0001_users");
677 let sqlite = step.up(Dialect::Sqlite).join("");
678 let mysql = step.up(Dialect::MySql).join("");
679 assert_ne!(sqlite, mysql, "each dialect should render its own DDL");
680 }
681
682 #[test]
683 fn an_irreversible_step_says_why_rather_than_doing_nothing() {
684 let step = Step::raw_irreversible(
686 "0003_backfill",
687 vec!["UPDATE users SET email = lower(email)".into()],
688 "the original casing is not recoverable",
689 );
690
691 let down = step.down(Dialect::Sqlite);
692 assert!(!down.is_reversible());
693
694 let err = down.sql("0003_backfill").unwrap_err();
695 assert!(err.message().contains("cannot be rolled back"), "{}", err.message());
696 assert!(err.message().contains("not recoverable"), "{}", err.message());
697 }
698
699 #[test]
700 fn a_custom_type_can_implement_the_contract() {
701 struct Citext;
702
703 impl Migration for Citext {
704 fn name(&self) -> &str {
705 "0004_citext"
706 }
707
708 fn up(&self, dialect: Dialect) -> Vec<String> {
709 match dialect {
710 Dialect::Postgres => vec!["CREATE EXTENSION IF NOT EXISTS citext".into()],
711 _ => Vec::new(),
712 }
713 }
714
715 fn down(&self, dialect: Dialect) -> Down {
716 match dialect {
717 Dialect::Postgres => Down::statements(["DROP EXTENSION citext".to_string()]),
718 _ => Down::statements([]),
719 }
720 }
721 }
722
723 let migrator = Migrator::new().add(Citext);
724
725 assert_eq!(migrator.names(), vec!["0004_citext"]);
726 assert!(migrator.irreversible(Dialect::Postgres).is_empty());
727 }
728
729 #[test]
730 fn irreversible_steps_can_be_listed_before_a_deploy() {
731 let migrator = Migrator::new().create_table::<User>("0001_users").raw_irreversible(
732 "0002_drop_legacy",
733 vec!["ALTER TABLE users DROP COLUMN legacy".into()],
734 "the column's data is gone",
735 );
736
737 assert_eq!(migrator.irreversible(Dialect::Sqlite), vec!["0002_drop_legacy"]);
738 }
739
740 #[tokio::test]
741 async fn runs_pending_migrations_in_order() {
742 let (db, connection) = fake_database(MemoryConnection::new(Dialect::Sqlite));
743
744 let ran = Migrator::new()
745 .raw("0001_a", vec!["CREATE TABLE a (id INT)".into()], vec!["DROP TABLE a".into()])
746 .raw("0002_b", vec!["CREATE TABLE b (id INT)".into()], vec!["DROP TABLE b".into()])
747 .run(&db)
748 .await
749 .unwrap();
750
751 assert_eq!(ran, vec!["0001_a", "0002_b"]);
752
753 let statements = connection.statements();
754 assert!(statements.iter().any(|s| s.contains("CREATE TABLE a")));
755 assert!(statements.iter().any(|s| s.contains("CREATE TABLE b")));
756 assert!(statements.iter().any(|s| s.contains("rainier_migrations")));
757 }
758
759 #[tokio::test]
760 async fn an_already_applied_migration_is_skipped() {
761 let connection = MemoryConnection::new(Dialect::Sqlite).returning([ledger("0001_a", 1)]);
764 let (db, handle) = fake_database(connection);
765
766 let ran = Migrator::new()
767 .raw("0001_a", vec!["CREATE TABLE a (id INT)".into()], vec!["DROP TABLE a".into()])
768 .raw("0002_b", vec!["CREATE TABLE b (id INT)".into()], vec!["DROP TABLE b".into()])
769 .run(&db)
770 .await
771 .unwrap();
772
773 assert_eq!(ran, vec!["0002_b"]);
774 assert!(
775 !handle.statements().iter().any(|s| s.contains("CREATE TABLE a")),
776 "the applied migration must not run again"
777 );
778 }
779
780 #[tokio::test]
781 async fn a_new_run_gets_the_next_batch_number() {
782 let (db, connection) =
783 fake_database(MemoryConnection::new(Dialect::Sqlite).returning([ledger("0001_a", 7)]));
784
785 Migrator::new()
786 .raw("0001_a", vec!["SELECT 1".into()], vec!["SELECT 1".into()])
787 .raw("0002_b", vec!["SELECT 2".into()], vec!["SELECT 2".into()])
788 .run(&db)
789 .await
790 .unwrap();
791
792 let inserts: Vec<_> = connection
793 .recorded()
794 .into_iter()
795 .filter(|r| r.sql.starts_with("INSERT INTO rainier_migrations"))
796 .collect();
797
798 assert_eq!(inserts.len(), 1, "only the pending step should be recorded");
799 assert_eq!(
800 inserts[0].params[1],
801 8_i64.into(),
802 "one past the highest batch in the ledger, not one past its row count"
803 );
804 }
805
806 #[tokio::test]
807 async fn a_failing_step_names_itself_and_its_statement() {
808 let (db, _) = fake_database(MemoryConnection::new(Dialect::Sqlite).failing("syntax error"));
809
810 let err = Migrator::new()
811 .raw("0001_broken", vec!["CREATE TABLE (".into()], vec!["SELECT 1".into()])
812 .run(&db)
813 .await
814 .unwrap_err();
815
816 assert!(err.message().contains("syntax error"), "{}", err.message());
819 }
820
821 #[tokio::test]
822 async fn the_ledger_is_created_before_anything_is_read() {
823 let (db, connection) = fake_database(MemoryConnection::new(Dialect::Sqlite));
824 Migrator::new().run(&db).await.unwrap();
825
826 let statements = connection.statements();
827 assert!(statements[0].contains("CREATE TABLE IF NOT EXISTS rainier_migrations"));
828 assert!(statements[0].contains("batch"), "{}", statements[0]);
829 }
830
831 #[test]
832 fn the_migrator_lists_its_steps() {
833 let migrator = Migrator::new().create_table::<User>("0001_users").raw(
834 "0002_seed",
835 vec!["INSERT INTO users DEFAULT VALUES".into()],
836 vec!["DELETE FROM users".into()],
837 );
838
839 assert_eq!(migrator.names(), vec!["0001_users", "0002_seed"]);
840 assert_eq!(migrator.len(), 2);
841 assert!(!migrator.is_empty());
842 }
843
844 #[test]
845 fn merging_appends_the_other_migrators_steps_in_order() {
846 let component = Migrator::new()
847 .raw("component_0001", vec!["SELECT 1".into()], vec!["SELECT 1".into()])
848 .raw("component_0002", vec!["SELECT 2".into()], vec!["SELECT 2".into()]);
849
850 let migrator = Migrator::new()
851 .raw("app_0001", vec!["SELECT 3".into()], vec!["SELECT 3".into()])
852 .merge(component)
853 .raw("app_0002", vec!["SELECT 4".into()], vec!["SELECT 4".into()]);
854
855 assert_eq!(
856 migrator.names(),
857 vec!["app_0001", "component_0001", "component_0002", "app_0002"],
858 "merged steps keep their order, at the point they were merged in"
859 );
860 }
861
862 #[tokio::test]
863 async fn postgres_gets_numbered_placeholders() {
864 let (db, connection) = fake_database(MemoryConnection::new(Dialect::Postgres));
865 Migrator::new()
866 .raw("0001_a", vec!["SELECT 1".into()], vec!["SELECT 1".into()])
867 .run(&db)
868 .await
869 .unwrap();
870
871 let insert = connection
872 .statements()
873 .into_iter()
874 .find(|s| s.starts_with("INSERT INTO rainier_migrations"))
875 .expect("the ledger row should have been written");
876 assert!(insert.contains("$1") && insert.contains("$2"), "{insert}");
877 }
878
879 #[tokio::test]
880 async fn rolling_back_an_empty_ledger_is_not_an_error() {
881 let (db, _) = fake_database(MemoryConnection::new(Dialect::Sqlite));
882 let migrator = Migrator::new().create_table::<User>("0001_users");
883
884 assert!(migrator.rollback(&db, 1).await.unwrap().is_empty());
885 }
886
887 #[tokio::test]
888 async fn rolling_back_zero_batches_does_nothing() {
889 let (db, connection) =
890 fake_database(MemoryConnection::new(Dialect::Sqlite).returning([ledger("0001_a", 1)]));
891
892 let migrator =
893 Migrator::new().raw("0001_a", vec!["SELECT 1".into()], vec!["DROP TABLE a".into()]);
894
895 assert!(migrator.rollback(&db, 0).await.unwrap().is_empty());
896 assert!(
897 !connection.statements().iter().any(|s| s.contains("DROP TABLE a")),
898 "nothing should have been undone"
899 );
900 }
901
902 #[tokio::test]
903 async fn a_rollback_undoes_the_last_batch_in_reverse() {
904 let (db, connection) = fake_database(MemoryConnection::new(Dialect::Sqlite).returning([
905 ledger("0001_a", 1),
906 ledger("0002_b", 2),
907 ledger("0003_c", 2),
908 ]));
909
910 let migrator = Migrator::new()
911 .raw("0001_a", vec!["SELECT 1".into()], vec!["DROP TABLE a".into()])
912 .raw("0002_b", vec!["SELECT 2".into()], vec!["DROP TABLE b".into()])
913 .raw("0003_c", vec!["SELECT 3".into()], vec!["DROP TABLE c".into()]);
914
915 let rolled_back = migrator.rollback(&db, 1).await.unwrap();
916
917 assert_eq!(
918 rolled_back,
919 vec!["0003_c", "0002_b"],
920 "the newest batch only, and dependants first"
921 );
922
923 let statements = connection.statements();
924 assert!(statements.iter().any(|s| s == "DROP TABLE c"));
925 assert!(statements.iter().any(|s| s == "DROP TABLE b"));
926 assert!(!statements.iter().any(|s| s == "DROP TABLE a"), "batch 1 was not in range");
927 assert!(statements.iter().any(|s| s.starts_with("DELETE FROM rainier_migrations")));
928 }
929
930 #[tokio::test]
931 async fn a_rollback_can_take_several_batches() {
932 let (db, _) = fake_database(
933 MemoryConnection::new(Dialect::Sqlite)
934 .returning([ledger("0001_a", 1), ledger("0002_b", 2)]),
935 );
936
937 let migrator = Migrator::new()
938 .raw("0001_a", vec!["SELECT 1".into()], vec!["DROP TABLE a".into()])
939 .raw("0002_b", vec!["SELECT 2".into()], vec!["DROP TABLE b".into()]);
940
941 assert_eq!(migrator.rollback(&db, 2).await.unwrap(), vec!["0002_b", "0001_a"]);
942 }
943
944 #[tokio::test]
945 async fn an_irreversible_step_stops_the_whole_rollback_before_it_starts() {
946 let (db, connection) = fake_database(
950 MemoryConnection::new(Dialect::Sqlite)
951 .returning([ledger("0001_a", 1), ledger("0002_b", 1)]),
952 );
953
954 let migrator = Migrator::new()
955 .raw_irreversible("0001_a", vec!["SELECT 1".into()], "the rows are gone")
956 .raw("0002_b", vec!["SELECT 2".into()], vec!["DROP TABLE b".into()]);
957
958 let err = migrator.rollback(&db, 1).await.unwrap_err();
959
960 assert!(err.message().contains("0001_a"), "{}", err.message());
961 assert!(err.message().contains("the rows are gone"), "{}", err.message());
962 assert!(
963 !connection.statements().iter().any(|s| s == "DROP TABLE b"),
964 "no step should have run"
965 );
966 }
967
968 #[tokio::test]
969 async fn rolling_back_a_migration_that_was_deleted_from_the_code_says_so() {
970 let (db, _) =
971 fake_database(MemoryConnection::new(Dialect::Sqlite).returning([ledger("0001_a", 1)]));
972
973 let err = Migrator::new().rollback(&db, 1).await.unwrap_err();
974
975 assert!(err.message().contains("0001_a"), "{}", err.message());
976 assert!(err.message().contains("not in this migrator"), "{}", err.message());
977 }
978
979 #[tokio::test]
980 async fn pending_is_what_has_not_run() {
981 let (db, _) =
982 fake_database(MemoryConnection::new(Dialect::Sqlite).returning([ledger("0001_a", 1)]));
983
984 let migrator = Migrator::new()
985 .raw("0001_a", vec!["SELECT 1".into()], vec!["SELECT 1".into()])
986 .raw("0002_b", vec!["SELECT 2".into()], vec!["SELECT 2".into()]);
987
988 assert_eq!(migrator.pending(&db).await.unwrap(), vec!["0002_b"]);
989 }
990}