feat: teach sqlx-cli about migrate.migrations-dir

This commit is contained in:
Austin Bonander
2025-01-15 10:31:03 -08:00
parent 367f2cca98
commit 1ff6a8a950
9 changed files with 163 additions and 91 deletions

View File

@@ -1,5 +1,5 @@
use crate::migrate;
use crate::opt::ConnectOpts;
use crate::{migrate, Config};
use crate::opt::{ConnectOpts, MigrationSourceOpt};
use console::style;
use promptly::{prompt, ReadlineError};
use sqlx::any::Any;
@@ -44,18 +44,19 @@ pub async fn drop(connect_opts: &ConnectOpts, confirm: bool, force: bool) -> any
}
pub async fn reset(
migration_source: &str,
config: &Config,
migration_source: &MigrationSourceOpt,
connect_opts: &ConnectOpts,
confirm: bool,
force: bool,
) -> anyhow::Result<()> {
drop(connect_opts, confirm, force).await?;
setup(migration_source, connect_opts).await
setup(config, migration_source, connect_opts).await
}
pub async fn setup(migration_source: &str, connect_opts: &ConnectOpts) -> anyhow::Result<()> {
pub async fn setup(config: &Config, migration_source: &MigrationSourceOpt, connect_opts: &ConnectOpts) -> anyhow::Result<()> {
create(connect_opts).await?;
migrate::run(migration_source, connect_opts, false, false, None).await
migrate::run(config, migration_source, connect_opts, false, false, None).await
}
fn ask_to_continue_drop(db_url: &str) -> bool {

View File

@@ -39,6 +39,7 @@ pub async fn run(opt: Opt) -> Result<()> {
connect_opts.populate_db_url(config)?;
migrate::run(
config,
&source,
&connect_opts,
dry_run,
@@ -57,6 +58,7 @@ pub async fn run(opt: Opt) -> Result<()> {
connect_opts.populate_db_url(config)?;
migrate::revert(
config,
&source,
&connect_opts,
dry_run,
@@ -71,9 +73,9 @@ pub async fn run(opt: Opt) -> Result<()> {
} => {
connect_opts.populate_db_url(config)?;
migrate::info(&source, &connect_opts).await?
migrate::info(config, &source, &connect_opts).await?
},
MigrateCommand::BuildScript { source, force } => migrate::build_script(&source, force)?,
MigrateCommand::BuildScript { source, force } => migrate::build_script(config, &source, force)?,
},
Command::Database(database) => match database.command {
@@ -96,14 +98,14 @@ pub async fn run(opt: Opt) -> Result<()> {
force,
} => {
connect_opts.populate_db_url(config)?;
database::reset(&source, &connect_opts, !confirmation.yes, force).await?
database::reset(config, &source, &connect_opts, !confirmation.yes, force).await?
},
DatabaseCommand::Setup {
source,
mut connect_opts,
} => {
connect_opts.populate_db_url(config)?;
database::setup(&source, &connect_opts).await?
database::setup(config, &source, &connect_opts).await?
},
},

View File

@@ -1,7 +1,7 @@
use crate::opt::{AddMigrationOpts, ConnectOpts};
use crate::opt::{AddMigrationOpts, ConnectOpts, MigrationSourceOpt};
use anyhow::{bail, Context};
use console::style;
use sqlx::migrate::{AppliedMigration, Migrate, MigrateError, MigrationType, Migrator};
use sqlx::migrate::{AppliedMigration, Migrate, MigrateError, MigrationType, Migrator, ResolveWith};
use sqlx::Connection;
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
@@ -11,58 +11,34 @@ use std::path::Path;
use std::time::Duration;
use crate::config::Config;
fn create_file(
migration_source: &str,
file_prefix: &str,
description: &str,
migration_type: MigrationType,
) -> anyhow::Result<()> {
use std::path::PathBuf;
let mut file_name = file_prefix.to_string();
file_name.push('_');
file_name.push_str(&description.replace(' ', "_"));
file_name.push_str(migration_type.suffix());
let mut path = PathBuf::new();
path.push(migration_source);
path.push(&file_name);
println!("Creating {}", style(path.display()).cyan());
let mut file = File::create(&path).context("Failed to create migration file")?;
std::io::Write::write_all(&mut file, migration_type.file_content().as_bytes())?;
Ok(())
}
pub async fn add(
config: &Config,
opts: AddMigrationOpts,
) -> anyhow::Result<()> {
fs::create_dir_all(&opts.source).context("Unable to create migrations directory")?;
let source = opts.source.resolve(config);
fs::create_dir_all(source).context("Unable to create migrations directory")?;
let migrator = Migrator::new(opts.source.as_ref()).await?;
let migrator = Migrator::new(Path::new(source)).await?;
let version_prefix = opts.version_prefix(config, &migrator);
if opts.reversible(config, &migrator) {
create_file(
&opts.source,
source,
&version_prefix,
&opts.description,
MigrationType::ReversibleUp,
)?;
create_file(
&opts.source,
source,
&version_prefix,
&opts.description,
MigrationType::ReversibleDown,
)?;
} else {
create_file(
&opts.source,
source,
&version_prefix,
&opts.description,
MigrationType::Simple,
@@ -70,13 +46,13 @@ pub async fn add(
}
// if the migrations directory is empty
let has_existing_migrations = fs::read_dir(&opts.source)
let has_existing_migrations = fs::read_dir(source)
.map(|mut dir| dir.next().is_some())
.unwrap_or(false);
if !has_existing_migrations {
let quoted_source = if *opts.source != "migrations" {
format!("{:?}", *opts.source)
let quoted_source = if opts.source.source.is_some() {
format!("{source:?}")
} else {
"".to_string()
};
@@ -114,6 +90,32 @@ See: https://docs.rs/sqlx/{version}/sqlx/macro.migrate.html
Ok(())
}
fn create_file(
migration_source: &str,
file_prefix: &str,
description: &str,
migration_type: MigrationType,
) -> anyhow::Result<()> {
use std::path::PathBuf;
let mut file_name = file_prefix.to_string();
file_name.push('_');
file_name.push_str(&description.replace(' ', "_"));
file_name.push_str(migration_type.suffix());
let mut path = PathBuf::new();
path.push(migration_source);
path.push(&file_name);
println!("Creating {}", style(path.display()).cyan());
let mut file = File::create(&path).context("Failed to create migration file")?;
std::io::Write::write_all(&mut file, migration_type.file_content().as_bytes())?;
Ok(())
}
fn short_checksum(checksum: &[u8]) -> String {
let mut s = String::with_capacity(checksum.len() * 2);
for b in checksum {
@@ -122,8 +124,10 @@ fn short_checksum(checksum: &[u8]) -> String {
s
}
pub async fn info(migration_source: &str, connect_opts: &ConnectOpts) -> anyhow::Result<()> {
let migrator = Migrator::new(Path::new(migration_source)).await?;
pub async fn info(config: &Config, migration_source: &MigrationSourceOpt, connect_opts: &ConnectOpts) -> anyhow::Result<()> {
let source = migration_source.resolve(config);
let migrator = Migrator::new(ResolveWith(Path::new(source), config.migrate.to_resolve_config())).await?;
let mut conn = crate::connect(connect_opts).await?;
conn.ensure_migrations_table().await?;
@@ -202,13 +206,16 @@ fn validate_applied_migrations(
}
pub async fn run(
migration_source: &str,
config: &Config,
migration_source: &MigrationSourceOpt,
connect_opts: &ConnectOpts,
dry_run: bool,
ignore_missing: bool,
target_version: Option<i64>,
) -> anyhow::Result<()> {
let migrator = Migrator::new(Path::new(migration_source)).await?;
let source = migration_source.resolve(config);
let migrator = Migrator::new(Path::new(source)).await?;
if let Some(target_version) = target_version {
if !migrator.version_exists(target_version) {
bail!(MigrateError::VersionNotPresent(target_version));
@@ -295,13 +302,15 @@ pub async fn run(
}
pub async fn revert(
migration_source: &str,
config: &Config,
migration_source: &MigrationSourceOpt,
connect_opts: &ConnectOpts,
dry_run: bool,
ignore_missing: bool,
target_version: Option<i64>,
) -> anyhow::Result<()> {
let migrator = Migrator::new(Path::new(migration_source)).await?;
let source = migration_source.resolve(config);
let migrator = Migrator::new(Path::new(source)).await?;
if let Some(target_version) = target_version {
if target_version != 0 && !migrator.version_exists(target_version) {
bail!(MigrateError::VersionNotPresent(target_version));
@@ -388,7 +397,9 @@ pub async fn revert(
Ok(())
}
pub fn build_script(migration_source: &str, force: bool) -> anyhow::Result<()> {
pub fn build_script(config: &Config, migration_source: &MigrationSourceOpt, force: bool) -> anyhow::Result<()> {
let source = migration_source.resolve(config);
anyhow::ensure!(
Path::new("Cargo.toml").exists(),
"must be run in a Cargo project root"
@@ -403,7 +414,7 @@ pub fn build_script(migration_source: &str, force: bool) -> anyhow::Result<()> {
r#"// generated by `sqlx migrate build-script`
fn main() {{
// trigger recompilation when a new migration is added
println!("cargo:rerun-if-changed={migration_source}");
println!("cargo:rerun-if-changed={source}");
}}
"#,
);

View File

@@ -1,6 +1,5 @@
use std::env;
use std::ops::{Deref, Not};
use std::path::Path;
use anyhow::Context;
use chrono::Utc;
use clap::{Args, Parser};
@@ -98,7 +97,7 @@ pub enum DatabaseCommand {
confirmation: Confirmation,
#[clap(flatten)]
source: Source,
source: MigrationSourceOpt,
#[clap(flatten)]
connect_opts: ConnectOpts,
@@ -111,7 +110,7 @@ pub enum DatabaseCommand {
/// Creates the database specified in your DATABASE_URL and runs any pending migrations.
Setup {
#[clap(flatten)]
source: Source,
source: MigrationSourceOpt,
#[clap(flatten)]
connect_opts: ConnectOpts,
@@ -197,7 +196,7 @@ pub enum MigrateCommand {
/// Run all pending migrations.
Run {
#[clap(flatten)]
source: Source,
source: MigrationSourceOpt,
/// List all the migrations to be run without applying
#[clap(long)]
@@ -218,7 +217,7 @@ pub enum MigrateCommand {
/// Revert the latest migration with a down file.
Revert {
#[clap(flatten)]
source: Source,
source: MigrationSourceOpt,
/// List the migration to be reverted without applying
#[clap(long)]
@@ -240,7 +239,7 @@ pub enum MigrateCommand {
/// List all available migrations.
Info {
#[clap(flatten)]
source: Source,
source: MigrationSourceOpt,
#[clap(flatten)]
connect_opts: ConnectOpts,
@@ -251,7 +250,7 @@ pub enum MigrateCommand {
/// Must be run in a Cargo project root.
BuildScript {
#[clap(flatten)]
source: Source,
source: MigrationSourceOpt,
/// Overwrite the build script if it already exists.
#[clap(long)]
@@ -264,7 +263,7 @@ pub struct AddMigrationOpts {
pub description: String,
#[clap(flatten)]
pub source: Source,
pub source: MigrationSourceOpt,
/// If set, create an up-migration only. Conflicts with `--reversible`.
#[clap(long, conflicts_with = "reversible")]
@@ -289,23 +288,21 @@ pub struct AddMigrationOpts {
/// Argument for the migration scripts source.
#[derive(Args, Debug)]
pub struct Source {
pub struct MigrationSourceOpt {
/// Path to folder containing migrations.
#[clap(long, default_value = "migrations")]
source: String,
///
/// Defaults to `migrations/` if not specified, but a different default may be set by `sqlx.toml`.
#[clap(long)]
pub source: Option<String>,
}
impl Deref for Source {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.source
}
}
impl AsRef<Path> for Source {
fn as_ref(&self) -> &Path {
Path::new(&self.source)
impl MigrationSourceOpt {
pub fn resolve<'a>(&'a self, config: &'a Config) -> &'a str {
if let Some(source) = &self.source {
return source;
}
config.migrate.migrations_dir()
}
}