Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow specifying multiple migration sources in CLI #3177

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions sqlx-cli/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,18 @@ pub async fn drop(connect_opts: &ConnectOpts, confirm: bool, force: bool) -> any
}

pub async fn reset(
migration_source: &str,
migration_sources: &[String],
connect_opts: &ConnectOpts,
confirm: bool,
force: bool,
) -> anyhow::Result<()> {
drop(connect_opts, confirm, force).await?;
setup(migration_source, connect_opts).await
setup(migration_sources, connect_opts).await
}

pub async fn setup(migration_source: &str, connect_opts: &ConnectOpts) -> anyhow::Result<()> {
pub async fn setup(migration_sources: &[String], connect_opts: &ConnectOpts) -> anyhow::Result<()> {
create(connect_opts).await?;
migrate::run(migration_source, connect_opts, false, false, None).await
migrate::run(migration_sources, connect_opts, false, false, None).await
}

fn ask_to_continue_drop(db_url: &str) -> bool {
Expand Down
34 changes: 26 additions & 8 deletions sqlx-cli/src/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,12 @@ 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(migration_sources: &[String], connect_opts: &ConnectOpts) -> anyhow::Result<()> {
let paths: Vec<_> = migration_sources
.iter()
.map(|source| Path::new(source))
.collect();
let migrator = Migrator::new(paths).await?;
let mut conn = crate::connect(&connect_opts).await?;

conn.ensure_migrations_table().await?;
Expand Down Expand Up @@ -272,13 +276,17 @@ fn validate_applied_migrations(
}

pub async fn run(
migration_source: &str,
migration_sources: &[String],
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 paths: Vec<_> = migration_sources
.iter()
.map(|source| Path::new(source))
.collect();
let migrator = Migrator::new(paths).await?;
if let Some(target_version) = target_version {
if !migrator.version_exists(target_version) {
bail!(MigrateError::VersionNotPresent(target_version));
Expand Down Expand Up @@ -367,13 +375,17 @@ pub async fn run(
}

pub async fn revert(
migration_source: &str,
migration_sources: &[String],
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 paths: Vec<_> = migration_sources
.iter()
.map(|source| Path::new(source))
.collect();
let migrator = Migrator::new(paths).await?;
if let Some(target_version) = target_version {
if target_version != 0 && !migrator.version_exists(target_version) {
bail!(MigrateError::VersionNotPresent(target_version));
Expand Down Expand Up @@ -461,7 +473,7 @@ pub async fn revert(
Ok(())
}

pub fn build_script(migration_source: &str, force: bool) -> anyhow::Result<()> {
pub fn build_script(migration_sources: &[String], force: bool) -> anyhow::Result<()> {
anyhow::ensure!(
Path::new("Cargo.toml").exists(),
"must be run in a Cargo project root"
Expand All @@ -472,11 +484,17 @@ pub fn build_script(migration_source: &str, force: bool) -> anyhow::Result<()> {
"build.rs already exists; use --force to overwrite"
);

let instructions = migration_sources
.iter()
.map(|source| format!(r#"println!("cargo:rerun-if-changed={source}");"#))
.collect::<Vec<_>>()
.join("\n ");

let contents = format!(
r#"// generated by `sqlx migrate build-script`
fn main() {{
// trigger recompilation when a new migration is added
println!("cargo:rerun-if-changed={migration_source}");
{instructions}
}}"#,
);

Expand Down
28 changes: 22 additions & 6 deletions sqlx-cli/src/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ pub enum DatabaseCommand {
confirmation: Confirmation,

#[clap(flatten)]
source: Source,
source: Sources,

#[clap(flatten)]
connect_opts: ConnectOpts,
Expand All @@ -101,7 +101,7 @@ pub enum DatabaseCommand {
/// Creates the database specified in your DATABASE_URL and runs any pending migrations.
Setup {
#[clap(flatten)]
source: Source,
source: Sources,

#[clap(flatten)]
connect_opts: ConnectOpts,
Expand Down Expand Up @@ -156,7 +156,7 @@ pub enum MigrateCommand {
/// Run all pending migrations.
Run {
#[clap(flatten)]
source: Source,
source: Sources,

/// List all the migrations to be run without applying
#[clap(long)]
Expand All @@ -177,7 +177,7 @@ pub enum MigrateCommand {
/// Revert the latest migration with a down file.
Revert {
#[clap(flatten)]
source: Source,
source: Sources,

/// List the migration to be reverted without applying
#[clap(long)]
Expand All @@ -199,7 +199,7 @@ pub enum MigrateCommand {
/// List all available migrations.
Info {
#[clap(flatten)]
source: Source,
source: Sources,

#[clap(flatten)]
connect_opts: ConnectOpts,
Expand All @@ -210,7 +210,7 @@ pub enum MigrateCommand {
/// Must be run in a Cargo project root.
BuildScript {
#[clap(flatten)]
source: Source,
source: Sources,

/// Overwrite the build script if it already exists.
#[clap(long)]
Expand All @@ -234,6 +234,22 @@ impl Deref for Source {
}
}

/// Argument for the migration scripts sources.
#[derive(Args, Debug)]
pub struct Sources {
/// Paths to folders containing migrations.
#[clap(long, default_values_t = vec!["migrations".to_owned()])]
source: Vec<String>,
}

impl Deref for Sources {
type Target = Vec<String>;

fn deref(&self) -> &Self::Target {
&self.source
}
}

/// Argument for the database URL.
#[derive(Args, Debug)]
pub struct ConnectOpts {
Expand Down
19 changes: 19 additions & 0 deletions sqlx-core/src/migrate/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::error::BoxDynError;
use crate::migrate::{Migration, MigrationType};
use futures_core::future::BoxFuture;

use futures_util::future;
use std::borrow::Cow;
use std::fmt::Debug;
use std::fs;
Expand Down Expand Up @@ -44,6 +45,24 @@ impl MigrationSource<'static> for PathBuf {
}
}

impl<'s, S: MigrationSource<'s> + Send + 's> MigrationSource<'s> for Vec<S> {
fn resolve(self) -> BoxFuture<'s, Result<Vec<Migration>, BoxDynError>> {
Box::pin(async move {
let migration_sets: Vec<_> =
future::join_all(self.into_iter().map(MigrationSource::resolve))
.await
.into_iter()
.collect::<Result<_, _>>()?;

// Merge migration sets by version in ascending order
let mut migrations: Vec<_> = migration_sets.into_iter().flatten().collect();
migrations.sort_by_key(|migration| migration.version);

Ok(migrations)
})
}
}

#[derive(thiserror::Error, Debug)]
#[error("{message}")]
pub struct ResolveError {
Expand Down