Skip to main content

spawn_db/commands/migration/
apply.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 crate::migrator::Migrator;
6use crate::variables::Variables;
7use anyhow::{anyhow, Result};
8
9pub struct ApplyMigration {
10    pub migration: Option<String>,
11    pub pinned: bool,
12    pub variables: Option<Variables>,
13    pub yes: bool,
14    pub retry: bool,
15}
16
17impl TelemetryDescribe for ApplyMigration {
18    fn telemetry(&self) -> TelemetryInfo {
19        TelemetryInfo::new("migration apply").with_properties(vec![
20            ("opt_pinned", self.pinned.to_string()),
21            ("has_variables", self.variables.is_some().to_string()),
22            ("apply_all", self.migration.is_none().to_string()),
23        ])
24    }
25}
26
27impl Command for ApplyMigration {
28    async fn execute(&self, config: &Config) -> Result<Outcome> {
29        let migrations = match &self.migration {
30            Some(migration) => vec![migration.clone()],
31            None => match get_pending_and_confirm(config, "apply", self.yes).await? {
32                Some(pending) => pending,
33                None => return Ok(Outcome::AppliedMigrations),
34            },
35        };
36
37        for migration in migrations {
38            let mgrtr = Migrator::new(config, &migration, self.pinned);
39            match mgrtr.generate_streaming(self.variables.clone()).await {
40                Ok(streaming) => {
41                    let engine = config.new_engine().await?;
42                    let write_fn = streaming.into_writer_fn();
43                    match engine
44                        .migration_apply(
45                            &migration,
46                            write_fn,
47                            None,
48                            super::DEFAULT_NAMESPACE,
49                            self.retry,
50                        )
51                        .await
52                    {
53                        Ok(_) => {
54                            println!("Migration '{}' applied successfully", &migration);
55                        }
56                        Err(MigrationError::AlreadyApplied { info, .. }) => {
57                            println!(
58                                "Migration '{}' already applied (status: {}, checksum: {})",
59                                &migration, info.last_status, info.checksum
60                            );
61                        }
62                        Err(MigrationError::PreviousAttemptFailed { status, info, .. }) => {
63                            return Err(anyhow!(
64                                "Migration '{}' has a previous {} attempt (checksum: {}).\n\
65                                 Use `spawn migration apply --retry {}` to retry.",
66                                &migration,
67                                status,
68                                info.checksum,
69                                &migration,
70                            ));
71                        }
72                        Err(MigrationError::Database(e)) => {
73                            return Err(
74                                e.context(format!("Failed applying migration {}", &migration))
75                            );
76                        }
77                        Err(MigrationError::AdvisoryLock(e)) => {
78                            return Err(
79                                anyhow!("Unable to obtain advisory lock for migration").context(e)
80                            );
81                        }
82                        Err(e @ MigrationError::NotRecorded { .. }) => {
83                            return Err(anyhow!("{}", e));
84                        }
85                    }
86                }
87                Err(e) => {
88                    let context = if self.pinned {
89                        anyhow!(
90                            "Failed to generate migration '{}'. Is it pinned? \
91                             Run `spawn migration pin {}` or use `--no-pin` to apply without pinning.",
92                            &migration, &migration
93                        )
94                    } else {
95                        anyhow!("failed to generate migration '{}'", &migration)
96                    };
97                    return Err(e.context(context));
98                }
99            };
100        }
101        Ok(Outcome::AppliedMigrations)
102    }
103}