db_migrate/commands/
down.rs

1use crate::{migration::MigrationManager, CommandOutput};
2use anyhow::Result;
3use clap::Args;
4use colored::*;
5
6#[derive(Args)]
7pub struct DownCommand {
8    /// Number of migrations to rollback (default: 1)
9    #[arg(short, long, default_value = "1")]
10    count: usize,
11
12    /// Dry run mode - show what would be rolled back without executing
13    #[arg(long)]
14    dry_run: bool,
15
16    /// Force rollback even if DOWN section is missing (dangerous)
17    #[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        // Get the most recent migrations to rollback (reverse order)
33        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                        // Force rollback by just removing the record
60                        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}