1use clap::{Parser, Subcommand};
25
26#[derive(Parser, Debug)]
28#[command(name = "migratio-runner")]
29#[command(about = "Database migration runner")]
30pub struct CliArgs {
31 #[command(subcommand)]
32 pub command: Commands,
33}
34
35#[derive(Subcommand, Debug)]
37pub enum Commands {
38 Status,
40 Upgrade {
42 #[arg(long)]
44 to: Option<u32>,
45 },
46 Downgrade {
48 #[arg(long)]
50 to: u32,
51 },
52 History,
54 Preview,
56 List,
58}
59
60#[cfg(feature = "sqlite")]
61pub use sqlite::run_sqlite;
62
63#[cfg(feature = "sqlite")]
64mod sqlite {
65 use super::{CliArgs, Commands};
66 use migratio::sqlite::SqliteMigrator;
67 use rusqlite::Connection;
68
69 pub fn run_sqlite(
83 migrator: SqliteMigrator,
84 database_url: &str,
85 args: CliArgs,
86 ) -> Result<(), Box<dyn std::error::Error>> {
87 if !std::path::Path::new(database_url).exists() {
90 return Err(format!(
91 "Database file not found: {}\n\nTo create a new database, first create the file manually or use your application's initialization logic.",
92 database_url
93 ).into());
94 }
95
96 let mut conn = Connection::open(database_url)?;
97
98 match args.command {
99 Commands::Status => {
100 let version = migrator.get_current_version(&mut conn)?;
101 let pending = migrator.preview_upgrade(&mut conn)?;
102 println!("Current version: {}", version);
103 println!("Pending migrations: {}", pending.len());
104 for m in pending {
105 println!(" - {} (v{})", m.name(), m.version());
106 }
107 }
108 Commands::Upgrade { to } => {
109 let report = match to {
110 Some(target) => migrator.upgrade_to(&mut conn, target)?,
111 None => migrator.upgrade(&mut conn)?,
112 };
113 if report.migrations_run.is_empty() {
114 println!("No migrations to run.");
115 } else {
116 println!("Migrations run: {:?}", report.migrations_run);
117 }
118 }
119 Commands::Downgrade { to } => {
120 let report = migrator.downgrade(&mut conn, to)?;
121 if report.migrations_run.is_empty() {
122 println!("No migrations to roll back.");
123 } else {
124 println!("Migrations rolled back: {:?}", report.migrations_run);
125 }
126 }
127 Commands::History => {
128 let history = migrator.get_migration_history(&mut conn)?;
129 if history.is_empty() {
130 println!("No migrations have been applied yet.");
131 } else {
132 println!("Migration history:");
133 for entry in history {
134 println!(
135 " v{}: {} (applied {})",
136 entry.version, entry.name, entry.applied_at
137 );
138 }
139 }
140 }
141 Commands::Preview => {
142 let pending = migrator.preview_upgrade(&mut conn)?;
143 if pending.is_empty() {
144 println!("No pending migrations.");
145 } else {
146 println!("Pending migrations:");
147 for m in pending {
148 println!(" - {} (v{})", m.name(), m.version());
149 if let Some(desc) = m.description() {
150 println!(" {}", desc);
151 }
152 }
153 }
154 }
155 Commands::List => {
156 unreachable!("List command should be handled before run_sqlite");
159 }
160 }
161
162 Ok(())
163 }
164}
165
166#[cfg(feature = "mysql")]
167pub use mysql_support::run_mysql;
168
169#[cfg(feature = "mysql")]
170mod mysql_support {
171 use super::{CliArgs, Commands};
172 use migratio::mysql::MysqlMigrator;
173 use mysql::{Conn, Opts};
174
175 pub fn run_mysql(
189 migrator: MysqlMigrator,
190 database_url: &str,
191 args: CliArgs,
192 ) -> Result<(), Box<dyn std::error::Error>> {
193 let opts = Opts::from_url(database_url)?;
194 let mut conn = Conn::new(opts)?;
195
196 match args.command {
197 Commands::Status => {
198 let version = migrator.get_current_version(&mut conn)?;
199 let pending = migrator.preview_upgrade(&mut conn)?;
200 println!("Current version: {}", version);
201 println!("Pending migrations: {}", pending.len());
202 for m in pending {
203 println!(" - {} (v{})", m.name(), m.version());
204 }
205 }
206 Commands::Upgrade { to } => {
207 let report = match to {
208 Some(target) => migrator.upgrade_to(&mut conn, target)?,
209 None => migrator.upgrade(&mut conn)?,
210 };
211 if report.migrations_run.is_empty() {
212 println!("No migrations to run.");
213 } else {
214 println!("Migrations run: {:?}", report.migrations_run);
215 }
216 }
217 Commands::Downgrade { to } => {
218 let report = migrator.downgrade(&mut conn, to)?;
219 if report.migrations_run.is_empty() {
220 println!("No migrations to roll back.");
221 } else {
222 println!("Migrations rolled back: {:?}", report.migrations_run);
223 }
224 }
225 Commands::History => {
226 let history = migrator.get_migration_history(&mut conn)?;
227 if history.is_empty() {
228 println!("No migrations have been applied yet.");
229 } else {
230 println!("Migration history:");
231 for entry in history {
232 println!(
233 " v{}: {} (applied {})",
234 entry.version, entry.name, entry.applied_at
235 );
236 }
237 }
238 }
239 Commands::Preview => {
240 let pending = migrator.preview_upgrade(&mut conn)?;
241 if pending.is_empty() {
242 println!("No pending migrations.");
243 } else {
244 println!("Pending migrations:");
245 for m in pending {
246 println!(" - {} (v{})", m.name(), m.version());
247 if let Some(desc) = m.description() {
248 println!(" {}", desc);
249 }
250 }
251 }
252 }
253 Commands::List => {
254 unreachable!("List command should be handled before run_mysql");
257 }
258 }
259
260 Ok(())
261 }
262}