drizzle_cli/commands/
migrate.rs1use crate::config::{Config, Driver};
6use crate::error::CliError;
7use crate::output;
8
9#[derive(clap::Args, Debug, Clone, Copy, Default)]
10pub struct MigrateOptions {
11 #[arg(long)]
13 pub verify: bool,
14
15 #[arg(long)]
17 pub plan: bool,
18
19 #[arg(long)]
21 pub safe: bool,
22}
23
24pub fn run(config: &Config, db_name: Option<&str>, opts: MigrateOptions) -> Result<(), CliError> {
32 validate_mutex_opts(opts)?;
33
34 let db = config.database(db_name)?;
35
36 crate::commands::harness::print_db_header(config, db_name);
37
38 println!("{}", output::heading(migrate_heading(opts)));
39 println!();
40
41 let out_dir = db.migrations_dir();
42
43 if !out_dir.exists() {
45 println!(" {}", output::warning("No migrations directory found."));
46 println!(" Run 'drizzle generate' to create your first migration.");
47 return Ok(());
48 }
49
50 if matches!(db.driver, Some(Driver::DurableSqlite)) {
54 print_durable_sqlite_notice(out_dir);
55 return Ok(());
56 }
57
58 let credentials = db.credentials()?;
60
61 let Some(credentials) = credentials else {
62 print_missing_credentials_help();
63 return Ok(());
64 };
65
66 let plan = if opts.verify || opts.plan || opts.safe {
67 Some(crate::db::verify_migrations(
68 &credentials,
69 db.dialect,
70 out_dir,
71 db.migrations_table(),
72 db.migrations_schema(),
73 )?)
74 } else {
75 None
76 };
77
78 if let Some(plan) = &plan
79 && handle_plan_short_circuit(plan, opts)
80 {
81 return Ok(());
82 }
83
84 let result = crate::db::run_migrations(
86 &credentials,
87 db.dialect,
88 out_dir,
89 db.migrations_table(),
90 db.migrations_schema(),
91 )?;
92
93 print_migration_result(&result, opts.safe);
94 Ok(())
95}
96
97fn validate_mutex_opts(opts: MigrateOptions) -> Result<(), CliError> {
98 if opts.safe && opts.verify {
99 return Err(CliError::Other(
100 "--safe can't be combined with --verify".to_string(),
101 ));
102 }
103 if opts.safe && opts.plan {
104 return Err(CliError::Other(
105 "--safe can't be combined with --plan".to_string(),
106 ));
107 }
108 Ok(())
109}
110
111const fn migrate_heading(opts: MigrateOptions) -> &'static str {
112 if opts.verify {
113 "Verifying migrations..."
114 } else if opts.plan {
115 "Planning migrations..."
116 } else if opts.safe {
117 "Running safe migration flow..."
118 } else {
119 "Running migrations..."
120 }
121}
122
123fn print_durable_sqlite_notice(out_dir: &std::path::Path) {
124 println!(
125 "{}",
126 output::warning("Durable Objects SQLite runs inside the Workers runtime.")
127 );
128 println!();
129 println!(" The CLI can't apply migrations to a DO from outside.");
130 println!(
131 " Apply them at `DurableObject` init time by importing `{}/migrations.js`",
132 out_dir.display()
133 );
134 println!(" and running each statement against `state.storage().sql()`.");
135 println!();
136 println!(
137 " (This command only generates the SQL + JS bundle — run `drizzle generate` for that.)"
138 );
139}
140
141fn print_missing_credentials_help() {
142 println!("{}", output::warning("No database credentials configured."));
143 println!();
144 println!("Add credentials to your drizzle.config.toml:");
145 println!();
146 println!(" {}", output::muted("[dbCredentials]"));
147 println!(" {}", output::muted("url = \"./dev.db\""));
148 println!();
149 println!("Or use an environment variable:");
150 println!();
151 println!(" {}", output::muted("[dbCredentials]"));
152 println!(" {}", output::muted("url = { env = \"DATABASE_URL\" }"));
153}
154
155fn handle_plan_short_circuit(plan: &crate::db::MigrationPlan, opts: MigrateOptions) -> bool {
157 println!(
158 " {} {}",
159 output::label("Applied migrations:"),
160 plan.applied_count
161 );
162 println!(
163 " {} {} ({} statement(s))",
164 output::label("Pending migrations:"),
165 plan.pending_count,
166 plan.pending_statements
167 );
168
169 if !plan.pending_migrations.is_empty() {
170 println!(" {}", output::label("Pending tags:"));
171 for tag in &plan.pending_migrations {
172 println!(" {} {}", output::label("->"), tag);
173 }
174 }
175 println!();
176
177 if opts.verify {
178 println!("{}", output::success("Migration verification passed."));
179 return true;
180 }
181
182 if opts.plan {
183 println!("{}", output::success("Migration plan complete."));
184 return true;
185 }
186
187 if opts.safe && plan.pending_count == 0 {
188 println!(" {}", output::success("No pending migrations."));
189 println!();
190 println!("{}", output::success("Safe migration complete!"));
191 return true;
192 }
193
194 false
195}
196
197fn print_migration_result(result: &crate::db::MigrationResult, safe: bool) {
198 if result.applied_count == 0 {
199 println!(" {}", output::success("No pending migrations."));
200 } else {
201 println!(
202 " {} {} migration(s):",
203 output::success("Applied"),
204 result.applied_count
205 );
206 for hash in &result.applied_migrations {
207 println!(" {} {}", output::label("->"), hash);
208 }
209 }
210
211 println!();
212 if safe {
213 println!("{}", output::success("Safe migration complete!"));
214 } else {
215 println!("{}", output::success("Migrations complete!"));
216 }
217}