db_migrate/commands/
down.rs1use crate::{migration::MigrationManager, CommandOutput};
2use anyhow::Result;
3use clap::Args;
4use colored::*;
5
6#[derive(Args)]
7pub struct DownCommand {
8 #[arg(short, long, default_value = "1")]
10 count: usize,
11
12 #[arg(long)]
14 dry_run: bool,
15
16 #[arg(long)]
18 force: bool,
19}
20
21impl DownCommand {
22 pub async fn execute(&self, manager: &mut MigrationManager) -> Result<CommandOutput> {
23 let applied_migrations = manager.get_applied_migrations().await?;
24
25 if applied_migrations.is_empty() {
26 return Ok(CommandOutput::success(format!(
27 "{} No applied migrations to rollback",
28 "✅".green()
29 )));
30 }
31
32 let migrations_to_rollback: Vec<_> = applied_migrations
34 .into_iter()
35 .rev()
36 .take(self.count)
37 .collect();
38
39 if self.dry_run {
40 return self.show_dry_run(&migrations_to_rollback);
41 }
42
43 let mut rollback_count = 0;
44 let mut rolled_back_migrations = Vec::new();
45
46 for migration_record in &migrations_to_rollback {
47 match manager.rollback_migration(&migration_record.version).await {
48 Ok(_) => {
49 rollback_count += 1;
50 rolled_back_migrations.push(&migration_record.version);
51 println!(
52 "{} Rolled back migration: {}",
53 "✅".green(),
54 migration_record.version.bright_cyan()
55 );
56 }
57 Err(crate::MigrationError::RollbackError { version, reason }) => {
58 if self.force {
59 match manager.remove_migration_record(&version).await {
61 Ok(_) => {
62 rollback_count += 1;
63 rolled_back_migrations.push(&migration_record.version);
64 println!(
65 "{} Force rolled back migration: {} ({})",
66 "⚠️ ".yellow(),
67 version.bright_cyan(),
68 reason.dimmed()
69 );
70 }
71 Err(e) => {
72 let error_msg = format!(
73 "Failed to force rollback migration {}: {}",
74 version, e
75 );
76
77 return Ok(CommandOutput::success_with_data(
78 format!(
79 "{} Rolled back {} migration(s), failed on: {}",
80 if rollback_count > 0 { "⚠️ " } else { "❌" },
81 rollback_count,
82 version
83 ),
84 serde_json::json!({
85 "rollback_count": rollback_count,
86 "rolled_back_migrations": rolled_back_migrations,
87 "failed_migration": version,
88 "error": error_msg
89 })
90 ));
91 }
92 }
93 } else {
94 let error_msg = format!(
95 "Cannot rollback migration {}: {}. Use --force to remove the migration record anyway.",
96 version, reason
97 );
98
99 return Ok(CommandOutput::success_with_data(
100 format!(
101 "{} Rolled back {} migration(s), failed on: {}",
102 if rollback_count > 0 { "⚠️ " } else { "❌" },
103 rollback_count,
104 version
105 ),
106 serde_json::json!({
107 "rollback_count": rollback_count,
108 "rolled_back_migrations": rolled_back_migrations,
109 "failed_migration": version,
110 "error": error_msg
111 })
112 ));
113 }
114 }
115 Err(e) => {
116 let error_msg = format!(
117 "Failed to rollback migration {}: {}",
118 migration_record.version, e
119 );
120
121 return Ok(CommandOutput::success_with_data(
122 format!(
123 "{} Rolled back {} migration(s), failed on: {}",
124 if rollback_count > 0 { "⚠️ " } else { "❌" },
125 rollback_count,
126 migration_record.version
127 ),
128 serde_json::json!({
129 "rollback_count": rollback_count,
130 "rolled_back_migrations": rolled_back_migrations,
131 "failed_migration": migration_record.version,
132 "error": error_msg
133 })
134 ));
135 }
136 }
137 }
138
139 let message = if rollback_count == 1 {
140 format!("{} Rolled back 1 migration successfully", "🎉".green())
141 } else {
142 format!("{} Rolled back {} migrations successfully", "🎉".green(), rollback_count)
143 };
144
145 Ok(CommandOutput::success_with_data(
146 message,
147 serde_json::json!({
148 "rollback_count": rollback_count,
149 "rolled_back_migrations": rolled_back_migrations
150 })
151 ))
152 }
153
154 fn show_dry_run(&self, migrations: &[crate::MigrationRecord]) -> Result<CommandOutput> {
155 let mut output = vec![
156 format!("{} Dry run mode - showing migrations that would be rolled back:", "🔍".cyan()),
157 String::new(),
158 ];
159
160 for (i, migration) in migrations.iter().enumerate() {
161 output.push(format!(
162 "{}. {} - {} (applied at: {})",
163 i + 1,
164 migration.version.bright_cyan(),
165 migration.description,
166 crate::utils::format_timestamp(migration.applied_at).dimmed()
167 ));
168 }
169
170 if migrations.is_empty() {
171 output.push("No migrations would be rolled back.".to_string());
172 } else {
173 output.push(String::new());
174 output.push(format!(
175 "Total: {} migration(s) would be rolled back",
176 migrations.len()
177 ));
178 }
179
180 Ok(CommandOutput::success_with_data(
181 output.join("\n"),
182 serde_json::json!({
183 "dry_run": true,
184 "migrations_count": migrations.len(),
185 "migrations": migrations.iter().map(|m| {
186 serde_json::json!({
187 "version": m.version,
188 "description": m.description,
189 "applied_at": m.applied_at
190 })
191 }).collect::<Vec<_>>()
192 })
193 ))
194 }
195}