Skip to main content

spawn_db/commands/migration/
adopt.rs

1use crate::commands::migration::get_pending_and_confirm;
2use crate::commands::{Command, Outcome, TelemetryDescribe, TelemetryInfo};
3use crate::config::Config;
4use crate::engine::MigrationError;
5use anyhow::{anyhow, Result};
6use dialoguer::Editor;
7
8pub struct AdoptMigration {
9    pub migration: Option<String>,
10    pub yes: bool,
11    pub description: Option<String>,
12}
13
14impl TelemetryDescribe for AdoptMigration {
15    fn telemetry(&self) -> TelemetryInfo {
16        TelemetryInfo::new("migration adopt")
17    }
18}
19
20/// Prompt the user for a description using their preferred editor.
21/// Returns an error if the description is empty or the editor is aborted.
22fn prompt_description() -> Result<String> {
23    let description = Editor::new()
24        .require_save(true)
25        .edit("# Why is this migration being adopted?\n# Lines starting with # will be ignored.\n")?
26        .map(|s| {
27            s.lines()
28                .filter(|line| !line.starts_with('#'))
29                .collect::<Vec<_>>()
30                .join("\n")
31                .trim()
32                .to_string()
33        })
34        .unwrap_or_default();
35
36    if description.is_empty() {
37        return Err(anyhow!("A description is required when adopting migrations. Use --description or provide one in the editor."));
38    }
39
40    Ok(description)
41}
42
43impl Command for AdoptMigration {
44    async fn execute(&self, config: &Config) -> Result<Outcome> {
45        let migrations = match &self.migration {
46            Some(migration) => vec![migration.clone()],
47            None => match get_pending_and_confirm(config, "adopt", self.yes).await? {
48                Some(pending) => pending,
49                None => return Ok(Outcome::AdoptedMigration),
50            },
51        };
52
53        let description = match &self.description {
54            Some(desc) => {
55                if desc.trim().is_empty() {
56                    return Err(anyhow!(
57                        "A description is required when adopting migrations."
58                    ));
59                }
60                desc.clone()
61            }
62            None => prompt_description()?,
63        };
64
65        let engine = config.new_engine().await?;
66
67        for migration in &migrations {
68            match engine
69                .migration_adopt(migration, super::DEFAULT_NAMESPACE, &description)
70                .await
71            {
72                Ok(msg) => {
73                    println!("{}", msg);
74                }
75                Err(MigrationError::AlreadyApplied { info, .. }) => {
76                    println!(
77                        "Migration '{}' already applied (status: {}, activity: {})",
78                        migration, info.last_status, info.last_activity
79                    );
80                }
81                Err(e) => {
82                    return Err(
83                        anyhow!(e).context(format!("Failed adopting migration '{}'", migration))
84                    );
85                }
86            }
87        }
88
89        Ok(Outcome::AdoptedMigration)
90    }
91}