diff --git a/sqlx-cli/src/database.rs b/sqlx-cli/src/database.rs index 82dcfcfed9..849900eb9b 100644 --- a/sqlx-cli/src/database.rs +++ b/sqlx-cli/src/database.rs @@ -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 { diff --git a/sqlx-cli/src/migrate.rs b/sqlx-cli/src/migrate.rs index e939f5bec8..3bc8d13dfe 100644 --- a/sqlx-cli/src/migrate.rs +++ b/sqlx-cli/src/migrate.rs @@ -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?; @@ -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, ) -> 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)); @@ -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, ) -> 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)); @@ -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" @@ -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::>() + .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} }}"#, ); diff --git a/sqlx-cli/src/opt.rs b/sqlx-cli/src/opt.rs index be9ca6dac1..712a9ec519 100644 --- a/sqlx-cli/src/opt.rs +++ b/sqlx-cli/src/opt.rs @@ -88,7 +88,7 @@ pub enum DatabaseCommand { confirmation: Confirmation, #[clap(flatten)] - source: Source, + source: Sources, #[clap(flatten)] connect_opts: ConnectOpts, @@ -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, @@ -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)] @@ -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)] @@ -199,7 +199,7 @@ pub enum MigrateCommand { /// List all available migrations. Info { #[clap(flatten)] - source: Source, + source: Sources, #[clap(flatten)] connect_opts: ConnectOpts, @@ -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)] @@ -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, +} + +impl Deref for Sources { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.source + } +} + /// Argument for the database URL. #[derive(Args, Debug)] pub struct ConnectOpts { diff --git a/sqlx-core/src/migrate/source.rs b/sqlx-core/src/migrate/source.rs index b5e4b816c7..bb00c10904 100644 --- a/sqlx-core/src/migrate/source.rs +++ b/sqlx-core/src/migrate/source.rs @@ -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; @@ -44,6 +45,24 @@ impl MigrationSource<'static> for PathBuf { } } +impl<'s, S: MigrationSource<'s> + Send + 's> MigrationSource<'s> for Vec { + fn resolve(self) -> BoxFuture<'s, Result, BoxDynError>> { + Box::pin(async move { + let migration_sets: Vec<_> = + future::join_all(self.into_iter().map(MigrationSource::resolve)) + .await + .into_iter() + .collect::>()?; + + // 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 {