spawn_db/commands/migration/
apply.rs1use 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}