toasty_cli/migration/
apply.rs

1use super::HistoryFile;
2use crate::Config;
3use anyhow::Result;
4use clap::Parser;
5use console::style;
6use std::collections::HashSet;
7use std::fs;
8use toasty::Db;
9use toasty::schema::db::Migration;
10
11#[derive(Parser, Debug)]
12pub struct ApplyCommand {}
13
14impl ApplyCommand {
15    pub(crate) async fn run(self, db: &Db, config: &Config) -> Result<()> {
16        println!();
17        println!("  {}", style("Apply Migrations").cyan().bold().underlined());
18        println!();
19        println!(
20            "  {}",
21            style(format!(
22                "Connected to {}",
23                crate::utility::redact_url_password(&db.driver().url())
24            ))
25            .dim()
26        );
27        println!();
28
29        apply_migrations(db, config).await
30    }
31}
32
33pub(crate) async fn apply_migrations(db: &Db, config: &Config) -> Result<()> {
34    let history_path = config.migration.get_history_file_path();
35
36    // Load migration history
37    let history = HistoryFile::load_or_default(&history_path)?;
38
39    if history.migrations().is_empty() {
40        println!(
41            "  {}",
42            style("No migrations found in history file.")
43                .magenta()
44                .dim()
45        );
46        println!();
47        return Ok(());
48    }
49
50    // Get a connection to check which migrations have been applied
51    let mut conn = db.driver().connect().await?;
52
53    // Get list of already applied migrations
54    let applied_migrations = conn.applied_migrations().await?;
55    let applied_ids: HashSet<u64> = applied_migrations.iter().map(|m| m.id()).collect();
56
57    // Find migrations that haven't been applied yet
58    let pending_migrations: Vec<_> = history
59        .migrations()
60        .iter()
61        .filter(|m| !applied_ids.contains(&m.id))
62        .collect();
63
64    if pending_migrations.is_empty() {
65        println!(
66            "  {}",
67            style("All migrations are already applied. Database is up to date.")
68                .green()
69                .dim()
70        );
71        println!();
72        return Ok(());
73    }
74
75    let pending_count = pending_migrations.len();
76    println!(
77        "  {} Found {} pending migration(s) to apply",
78        style("→").cyan(),
79        pending_count
80    );
81    println!();
82
83    // Apply each pending migration
84    for migration_entry in &pending_migrations {
85        let migration_path = config
86            .migration
87            .get_migrations_dir()
88            .join(&migration_entry.name);
89
90        println!(
91            "  {} Applying migration: {}",
92            style("→").cyan(),
93            style(&migration_entry.name).bold()
94        );
95
96        // Load the migration SQL file
97        let sql = fs::read_to_string(&migration_path)?;
98        let migration = Migration::new_sql(sql);
99
100        // Apply the migration
101        conn.apply_migration(migration_entry.id, migration_entry.name.clone(), &migration)
102            .await?;
103
104        println!(
105            "  {} {}",
106            style("✓").green().bold(),
107            style(format!("Applied: {}", migration_entry.name)).dim()
108        );
109    }
110
111    println!();
112    println!(
113        "  {}",
114        style(format!(
115            "Successfully applied {} migration(s)",
116            pending_count
117        ))
118        .green()
119        .bold()
120    );
121    println!();
122
123    Ok(())
124}