mirror of
https://github.com/launchbadge/sqlx.git
synced 2026-03-19 08:39:44 +00:00
feat: introduce migrate.create-schemas
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use crate::{migrate, Config};
|
||||
use crate::opt::{ConnectOpts, MigrationSourceOpt};
|
||||
use crate::{migrate, Config};
|
||||
use console::style;
|
||||
use promptly::{prompt, ReadlineError};
|
||||
use sqlx::any::Any;
|
||||
@@ -54,7 +54,11 @@ pub async fn reset(
|
||||
setup(config, migration_source, connect_opts).await
|
||||
}
|
||||
|
||||
pub async fn setup(config: &Config, migration_source: &MigrationSourceOpt, 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(config, migration_source, connect_opts, false, false, None).await
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::io;
|
||||
use std::path::{PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
@@ -28,7 +28,7 @@ pub async fn run(opt: Opt) -> Result<()> {
|
||||
|
||||
match opt.command {
|
||||
Command::Migrate(migrate) => match migrate.command {
|
||||
MigrateCommand::Add(opts)=> migrate::add(config, opts).await?,
|
||||
MigrateCommand::Add(opts) => migrate::add(config, opts).await?,
|
||||
MigrateCommand::Run {
|
||||
source,
|
||||
dry_run,
|
||||
@@ -74,15 +74,17 @@ pub async fn run(opt: Opt) -> Result<()> {
|
||||
connect_opts.populate_db_url(config)?;
|
||||
|
||||
migrate::info(config, &source, &connect_opts).await?
|
||||
},
|
||||
MigrateCommand::BuildScript { source, force } => migrate::build_script(config, &source, force)?,
|
||||
}
|
||||
MigrateCommand::BuildScript { source, force } => {
|
||||
migrate::build_script(config, &source, force)?
|
||||
}
|
||||
},
|
||||
|
||||
Command::Database(database) => match database.command {
|
||||
DatabaseCommand::Create { mut connect_opts } => {
|
||||
connect_opts.populate_db_url(config)?;
|
||||
database::create(&connect_opts).await?
|
||||
},
|
||||
}
|
||||
DatabaseCommand::Drop {
|
||||
confirmation,
|
||||
mut connect_opts,
|
||||
@@ -90,7 +92,7 @@ pub async fn run(opt: Opt) -> Result<()> {
|
||||
} => {
|
||||
connect_opts.populate_db_url(config)?;
|
||||
database::drop(&connect_opts, !confirmation.yes, force).await?
|
||||
},
|
||||
}
|
||||
DatabaseCommand::Reset {
|
||||
confirmation,
|
||||
source,
|
||||
@@ -99,14 +101,14 @@ pub async fn run(opt: Opt) -> Result<()> {
|
||||
} => {
|
||||
connect_opts.populate_db_url(config)?;
|
||||
database::reset(config, &source, &connect_opts, !confirmation.yes, force).await?
|
||||
},
|
||||
}
|
||||
DatabaseCommand::Setup {
|
||||
source,
|
||||
mut connect_opts,
|
||||
} => {
|
||||
connect_opts.populate_db_url(config)?;
|
||||
database::setup(config, &source, &connect_opts).await?
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
Command::Prepare {
|
||||
@@ -118,7 +120,7 @@ pub async fn run(opt: Opt) -> Result<()> {
|
||||
} => {
|
||||
connect_opts.populate_db_url(config)?;
|
||||
prepare::run(check, all, workspace, connect_opts, args).await?
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(feature = "completions")]
|
||||
Command::Completions { shell } => completions::run(shell),
|
||||
@@ -183,6 +185,6 @@ async fn config_from_current_dir() -> anyhow::Result<&'static Config> {
|
||||
|
||||
Config::read_with_or_default(move || Ok(path))
|
||||
})
|
||||
.await
|
||||
.context("unexpected error loading config")
|
||||
.await
|
||||
.context("unexpected error loading config")
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use crate::config::Config;
|
||||
use crate::opt::{AddMigrationOpts, ConnectOpts, MigrationSourceOpt};
|
||||
use anyhow::{bail, Context};
|
||||
use console::style;
|
||||
use sqlx::migrate::{AppliedMigration, Migrate, MigrateError, MigrationType, Migrator, ResolveWith};
|
||||
use sqlx::migrate::{
|
||||
AppliedMigration, Migrate, MigrateError, MigrationType, Migrator, ResolveWith,
|
||||
};
|
||||
use sqlx::Connection;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@@ -9,14 +12,10 @@ use std::fmt::Write;
|
||||
use std::fs::{self, File};
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use crate::config::Config;
|
||||
|
||||
pub async fn add(
|
||||
config: &Config,
|
||||
opts: AddMigrationOpts,
|
||||
) -> anyhow::Result<()> {
|
||||
pub async fn add(config: &Config, opts: AddMigrationOpts) -> anyhow::Result<()> {
|
||||
let source = opts.source.resolve(config);
|
||||
|
||||
|
||||
fs::create_dir_all(source).context("Unable to create migrations directory")?;
|
||||
|
||||
let migrator = Migrator::new(Path::new(source)).await?;
|
||||
@@ -124,13 +123,27 @@ fn short_checksum(checksum: &[u8]) -> String {
|
||||
s
|
||||
}
|
||||
|
||||
pub async fn info(config: &Config, migration_source: &MigrationSourceOpt, connect_opts: &ConnectOpts) -> anyhow::Result<()> {
|
||||
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 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(config.migrate.table_name()).await?;
|
||||
// FIXME: we shouldn't actually be creating anything here
|
||||
for schema_name in &config.migrate.create_schemas {
|
||||
conn.create_schema_if_not_exists(schema_name).await?;
|
||||
}
|
||||
|
||||
conn.ensure_migrations_table(config.migrate.table_name())
|
||||
.await?;
|
||||
|
||||
let applied_migrations: HashMap<_, _> = conn
|
||||
.list_applied_migrations(config.migrate.table_name())
|
||||
@@ -214,7 +227,7 @@ pub async fn run(
|
||||
target_version: Option<i64>,
|
||||
) -> anyhow::Result<()> {
|
||||
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) {
|
||||
@@ -224,14 +237,21 @@ pub async fn run(
|
||||
|
||||
let mut conn = crate::connect(connect_opts).await?;
|
||||
|
||||
conn.ensure_migrations_table(config.migrate.table_name()).await?;
|
||||
for schema_name in &config.migrate.create_schemas {
|
||||
conn.create_schema_if_not_exists(schema_name).await?;
|
||||
}
|
||||
|
||||
conn.ensure_migrations_table(config.migrate.table_name())
|
||||
.await?;
|
||||
|
||||
let version = conn.dirty_version(config.migrate.table_name()).await?;
|
||||
if let Some(version) = version {
|
||||
bail!(MigrateError::Dirty(version));
|
||||
}
|
||||
|
||||
let applied_migrations = conn.list_applied_migrations(config.migrate.table_name()).await?;
|
||||
let applied_migrations = conn
|
||||
.list_applied_migrations(config.migrate.table_name())
|
||||
.await?;
|
||||
validate_applied_migrations(&applied_migrations, &migrator, ignore_missing)?;
|
||||
|
||||
let latest_version = applied_migrations
|
||||
@@ -319,14 +339,22 @@ pub async fn revert(
|
||||
|
||||
let mut conn = crate::connect(connect_opts).await?;
|
||||
|
||||
conn.ensure_migrations_table(config.migrate.table_name()).await?;
|
||||
// FIXME: we should not be creating anything here if it doesn't exist
|
||||
for schema_name in &config.migrate.create_schemas {
|
||||
conn.create_schema_if_not_exists(schema_name).await?;
|
||||
}
|
||||
|
||||
conn.ensure_migrations_table(config.migrate.table_name())
|
||||
.await?;
|
||||
|
||||
let version = conn.dirty_version(config.migrate.table_name()).await?;
|
||||
if let Some(version) = version {
|
||||
bail!(MigrateError::Dirty(version));
|
||||
}
|
||||
|
||||
let applied_migrations = conn.list_applied_migrations(config.migrate.table_name()).await?;
|
||||
let applied_migrations = conn
|
||||
.list_applied_migrations(config.migrate.table_name())
|
||||
.await?;
|
||||
validate_applied_migrations(&applied_migrations, &migrator, ignore_missing)?;
|
||||
|
||||
let latest_version = applied_migrations
|
||||
@@ -397,9 +425,13 @@ pub async fn revert(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn build_script(config: &Config, migration_source: &MigrationSourceOpt, 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"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::env;
|
||||
use std::ops::{Deref, Not};
|
||||
use crate::config::migrate::{DefaultMigrationType, DefaultVersioning};
|
||||
use crate::config::Config;
|
||||
use anyhow::Context;
|
||||
use chrono::Utc;
|
||||
use clap::{Args, Parser};
|
||||
#[cfg(feature = "completions")]
|
||||
use clap_complete::Shell;
|
||||
use crate::config::Config;
|
||||
use sqlx::migrate::Migrator;
|
||||
use crate::config::migrate::{DefaultMigrationType, DefaultVersioning};
|
||||
use std::env;
|
||||
use std::ops::{Deref, Not};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version, about, author)]
|
||||
@@ -129,7 +129,7 @@ pub enum MigrateCommand {
|
||||
/// Create a new migration with the given description.
|
||||
///
|
||||
/// --------------------------------
|
||||
///
|
||||
///
|
||||
/// Migrations may either be simple, or reversible.
|
||||
///
|
||||
/// Reversible migrations can be reverted with `sqlx migrate revert`, simple migrations cannot.
|
||||
@@ -152,7 +152,7 @@ pub enum MigrateCommand {
|
||||
/// It is recommended to always back up the database before running migrations.
|
||||
///
|
||||
/// --------------------------------
|
||||
///
|
||||
///
|
||||
/// For convenience, this command attempts to detect if reversible migrations are in-use.
|
||||
///
|
||||
/// If the latest existing migration is reversible, the new migration will also be reversible.
|
||||
@@ -164,7 +164,7 @@ pub enum MigrateCommand {
|
||||
/// The default type to use can also be set in `sqlx.toml`.
|
||||
///
|
||||
/// --------------------------------
|
||||
///
|
||||
///
|
||||
/// A version number will be automatically assigned to the migration.
|
||||
///
|
||||
/// Migrations are applied in ascending order by version number.
|
||||
@@ -174,9 +174,9 @@ pub enum MigrateCommand {
|
||||
/// less than _any_ previously applied migration.
|
||||
///
|
||||
/// Migrations should only be created with increasing version number.
|
||||
///
|
||||
///
|
||||
/// --------------------------------
|
||||
///
|
||||
///
|
||||
/// For convenience, this command will attempt to detect if sequential versioning is in use,
|
||||
/// and if so, continue the sequence.
|
||||
///
|
||||
@@ -290,7 +290,7 @@ pub struct AddMigrationOpts {
|
||||
#[derive(Args, Debug)]
|
||||
pub struct MigrationSourceOpt {
|
||||
/// Path to folder containing migrations.
|
||||
///
|
||||
///
|
||||
/// Defaults to `migrations/` if not specified, but a different default may be set by `sqlx.toml`.
|
||||
#[clap(long)]
|
||||
pub source: Option<String>,
|
||||
@@ -301,7 +301,7 @@ impl MigrationSourceOpt {
|
||||
if let Some(source) = &self.source {
|
||||
return source;
|
||||
}
|
||||
|
||||
|
||||
config.migrate.migrations_dir()
|
||||
}
|
||||
}
|
||||
@@ -335,7 +335,9 @@ impl ConnectOpts {
|
||||
/// Require a database URL to be provided, otherwise
|
||||
/// return an error.
|
||||
pub fn expect_db_url(&self) -> anyhow::Result<&str> {
|
||||
self.database_url.as_deref().context("BUG: database_url not populated")
|
||||
self.database_url
|
||||
.as_deref()
|
||||
.context("BUG: database_url not populated")
|
||||
}
|
||||
|
||||
/// Populate `database_url` from the environment, if not set.
|
||||
@@ -359,7 +361,7 @@ impl ConnectOpts {
|
||||
}
|
||||
|
||||
self.database_url = Some(url)
|
||||
},
|
||||
}
|
||||
Err(env::VarError::NotPresent) => {
|
||||
anyhow::bail!("`--database-url` or `{var}`{context} must be set")
|
||||
}
|
||||
@@ -407,22 +409,20 @@ impl Not for IgnoreMissing {
|
||||
|
||||
impl AddMigrationOpts {
|
||||
pub fn reversible(&self, config: &Config, migrator: &Migrator) -> bool {
|
||||
if self.reversible { return true; }
|
||||
if self.simple { return false; }
|
||||
if self.reversible {
|
||||
return true;
|
||||
}
|
||||
if self.simple {
|
||||
return false;
|
||||
}
|
||||
|
||||
match config.migrate.defaults.migration_type {
|
||||
DefaultMigrationType::Inferred => {
|
||||
migrator
|
||||
.iter()
|
||||
.last()
|
||||
.is_some_and(|m| m.migration_type.is_reversible())
|
||||
}
|
||||
DefaultMigrationType::Simple => {
|
||||
false
|
||||
}
|
||||
DefaultMigrationType::Reversible => {
|
||||
true
|
||||
}
|
||||
DefaultMigrationType::Inferred => migrator
|
||||
.iter()
|
||||
.last()
|
||||
.is_some_and(|m| m.migration_type.is_reversible()),
|
||||
DefaultMigrationType::Simple => false,
|
||||
DefaultMigrationType::Reversible => true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,8 +434,7 @@ impl AddMigrationOpts {
|
||||
}
|
||||
|
||||
if self.sequential || matches!(default_versioning, DefaultVersioning::Sequential) {
|
||||
return next_sequential(migrator)
|
||||
.unwrap_or_else(|| fmt_sequential(1));
|
||||
return next_sequential(migrator).unwrap_or_else(|| fmt_sequential(1));
|
||||
}
|
||||
|
||||
next_sequential(migrator).unwrap_or_else(next_timestamp)
|
||||
@@ -455,18 +454,16 @@ fn next_sequential(migrator: &Migrator) -> Option<String> {
|
||||
match migrations {
|
||||
[previous, latest] => {
|
||||
// If the latest two versions differ by 1, infer sequential.
|
||||
(latest.version - previous.version == 1)
|
||||
.then_some(latest.version + 1)
|
||||
},
|
||||
(latest.version - previous.version == 1).then_some(latest.version + 1)
|
||||
}
|
||||
[latest] => {
|
||||
// If only one migration exists and its version is 0 or 1, infer sequential
|
||||
matches!(latest.version, 0 | 1)
|
||||
.then_some(latest.version + 1)
|
||||
matches!(latest.version, 0 | 1).then_some(latest.version + 1)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
next_version.map(fmt_sequential)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use assert_cmd::{assert::Assert, Command};
|
||||
|
||||
use sqlx::_unstable::config::Config;
|
||||
use sqlx::{migrate::Migrate, Connection, SqliteConnection};
|
||||
use std::{
|
||||
env::temp_dir,
|
||||
fs::remove_file,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use sqlx::_unstable::config::Config;
|
||||
|
||||
pub struct TestDatabase {
|
||||
file_path: PathBuf,
|
||||
|
||||
Reference in New Issue
Block a user