Skip to main content

spawn_db/commands/migration/
status.rs

1use crate::commands::migration::get_combined_migration_status;
2use crate::commands::{Command, Outcome, TelemetryDescribe, TelemetryInfo};
3use crate::config::Config;
4use crate::engine::MigrationStatus as EngineStatus;
5use anyhow::Result;
6use console::style;
7use tabled::settings::Style;
8use tabled::{Table, Tabled};
9
10#[derive(Tabled)]
11struct MigrationStatusDisplay {
12    #[tabled(rename = "Migration")]
13    name: String,
14    #[tabled(rename = "Filesystem")]
15    on_filesystem: String,
16    #[tabled(rename = "Pinned")]
17    pinned: String,
18    #[tabled(rename = "Database")]
19    in_database: String,
20    #[tabled(rename = "Status")]
21    status: String,
22}
23
24pub struct MigrationStatus;
25
26impl TelemetryDescribe for MigrationStatus {
27    fn telemetry(&self) -> TelemetryInfo {
28        TelemetryInfo::new("migration status")
29    }
30}
31
32impl Command for MigrationStatus {
33    async fn execute(&self, config: &Config) -> Result<Outcome> {
34        let status_rows =
35            get_combined_migration_status(config, Some(super::DEFAULT_NAMESPACE)).await?;
36
37        if status_rows.is_empty() {
38            println!("No migrations found");
39            return Ok(Outcome::Success);
40        }
41
42        let display_rows: Vec<MigrationStatusDisplay> = status_rows
43            .into_iter()
44            .map(|row| {
45                let on_filesystem = if row.exists_in_filesystem {
46                    style("✓").green().to_string()
47                } else {
48                    style("✗").red().to_string()
49                };
50
51                let pinned = if row.is_pinned {
52                    style("✓").green().to_string()
53                } else {
54                    style("✗").red().to_string()
55                };
56
57                let in_database = if row.exists_in_db {
58                    style("✓").green().to_string()
59                } else {
60                    style("✗").red().to_string()
61                };
62
63                // Determine status display based on whether it's applied and how
64                let status = match (
65                    row.exists_in_db,
66                    row.last_status,
67                    row.last_activity.as_deref(),
68                ) {
69                    (true, Some(EngineStatus::Success), Some("APPLY")) => {
70                        style("✓ Applied").green().to_string()
71                    }
72                    (true, Some(EngineStatus::Success), Some("ADOPT")) => {
73                        style("⊙ Adopted").cyan().to_string()
74                    }
75                    (true, Some(EngineStatus::Attempted), _) => {
76                        style("⚠ Attempted").yellow().to_string()
77                    }
78                    (true, Some(EngineStatus::Failure), _) => style("✗ Failed").red().to_string(),
79                    (false, _, _) => style("○ Pending").dim().to_string(),
80                    _ => style("-").dim().to_string(),
81                };
82
83                MigrationStatusDisplay {
84                    name: row.migration_name,
85                    on_filesystem,
86                    pinned,
87                    in_database,
88                    status,
89                }
90            })
91            .collect();
92
93        let mut table = Table::new(display_rows);
94        table.with(Style::sharp());
95        println!("\n{}\n", table);
96
97        Ok(Outcome::Success)
98    }
99}