mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-12-29 21:00:54 +00:00
feat: teach sqlx-cli about migrate.defaults
This commit is contained in:
parent
e951d8e128
commit
367f2cca98
@ -1,5 +1,5 @@
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::{PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
@ -21,21 +21,14 @@ mod prepare;
|
||||
|
||||
pub use crate::opt::Opt;
|
||||
|
||||
pub use sqlx::_unstable::config;
|
||||
use crate::config::Config;
|
||||
pub use sqlx::_unstable::config::{self, Config};
|
||||
|
||||
pub async fn run(opt: Opt) -> Result<()> {
|
||||
let config = config_from_current_dir()?;
|
||||
let config = config_from_current_dir().await?;
|
||||
|
||||
match opt.command {
|
||||
Command::Migrate(migrate) => match migrate.command {
|
||||
MigrateCommand::Add {
|
||||
source,
|
||||
description,
|
||||
reversible,
|
||||
sequential,
|
||||
timestamp,
|
||||
} => migrate::add(&source, &description, reversible, sequential, timestamp).await?,
|
||||
MigrateCommand::Add(opts)=> migrate::add(config, opts).await?,
|
||||
MigrateCommand::Run {
|
||||
source,
|
||||
dry_run,
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
use crate::opt::ConnectOpts;
|
||||
use crate::opt::{AddMigrationOpts, ConnectOpts};
|
||||
use anyhow::{bail, Context};
|
||||
use chrono::Utc;
|
||||
use console::style;
|
||||
use sqlx::migrate::{AppliedMigration, Migrate, MigrateError, MigrationType, Migrator};
|
||||
use sqlx::Connection;
|
||||
@ -10,6 +9,7 @@ use std::fmt::Write;
|
||||
use std::fs::{self, File};
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use crate::config::Config;
|
||||
|
||||
fn create_file(
|
||||
migration_source: &str,
|
||||
@ -37,116 +37,46 @@ fn create_file(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
enum MigrationOrdering {
|
||||
Timestamp(String),
|
||||
Sequential(String),
|
||||
}
|
||||
|
||||
impl MigrationOrdering {
|
||||
fn timestamp() -> MigrationOrdering {
|
||||
Self::Timestamp(Utc::now().format("%Y%m%d%H%M%S").to_string())
|
||||
}
|
||||
|
||||
fn sequential(version: i64) -> MigrationOrdering {
|
||||
Self::Sequential(format!("{version:04}"))
|
||||
}
|
||||
|
||||
fn file_prefix(&self) -> &str {
|
||||
match self {
|
||||
MigrationOrdering::Timestamp(prefix) => prefix,
|
||||
MigrationOrdering::Sequential(prefix) => prefix,
|
||||
}
|
||||
}
|
||||
|
||||
fn infer(sequential: bool, timestamp: bool, migrator: &Migrator) -> Self {
|
||||
match (timestamp, sequential) {
|
||||
(true, true) => panic!("Impossible to specify both timestamp and sequential mode"),
|
||||
(true, false) => MigrationOrdering::timestamp(),
|
||||
(false, true) => MigrationOrdering::sequential(
|
||||
migrator
|
||||
.iter()
|
||||
.last()
|
||||
.map_or(1, |last_migration| last_migration.version + 1),
|
||||
),
|
||||
(false, false) => {
|
||||
// inferring the naming scheme
|
||||
let migrations = migrator
|
||||
.iter()
|
||||
.filter(|migration| migration.migration_type.is_up_migration())
|
||||
.rev()
|
||||
.take(2)
|
||||
.collect::<Vec<_>>();
|
||||
if let [last, pre_last] = &migrations[..] {
|
||||
// there are at least two migrations, compare the last twothere's only one existing migration
|
||||
if last.version - pre_last.version == 1 {
|
||||
// their version numbers differ by 1, infer sequential
|
||||
MigrationOrdering::sequential(last.version + 1)
|
||||
} else {
|
||||
MigrationOrdering::timestamp()
|
||||
}
|
||||
} else if let [last] = &migrations[..] {
|
||||
// there is only one existing migration
|
||||
if last.version == 0 || last.version == 1 {
|
||||
// infer sequential if the version number is 0 or 1
|
||||
MigrationOrdering::sequential(last.version + 1)
|
||||
} else {
|
||||
MigrationOrdering::timestamp()
|
||||
}
|
||||
} else {
|
||||
MigrationOrdering::timestamp()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add(
|
||||
migration_source: &str,
|
||||
description: &str,
|
||||
reversible: bool,
|
||||
sequential: bool,
|
||||
timestamp: bool,
|
||||
config: &Config,
|
||||
opts: AddMigrationOpts,
|
||||
) -> anyhow::Result<()> {
|
||||
fs::create_dir_all(migration_source).context("Unable to create migrations directory")?;
|
||||
fs::create_dir_all(&opts.source).context("Unable to create migrations directory")?;
|
||||
|
||||
let migrator = Migrator::new(Path::new(migration_source)).await?;
|
||||
// Type of newly created migration will be the same as the first one
|
||||
// or reversible flag if this is the first migration
|
||||
let migration_type = MigrationType::infer(&migrator, reversible);
|
||||
let migrator = Migrator::new(opts.source.as_ref()).await?;
|
||||
|
||||
let ordering = MigrationOrdering::infer(sequential, timestamp, &migrator);
|
||||
let file_prefix = ordering.file_prefix();
|
||||
let version_prefix = opts.version_prefix(config, &migrator);
|
||||
|
||||
if migration_type.is_reversible() {
|
||||
if opts.reversible(config, &migrator) {
|
||||
create_file(
|
||||
migration_source,
|
||||
file_prefix,
|
||||
description,
|
||||
&opts.source,
|
||||
&version_prefix,
|
||||
&opts.description,
|
||||
MigrationType::ReversibleUp,
|
||||
)?;
|
||||
create_file(
|
||||
migration_source,
|
||||
file_prefix,
|
||||
description,
|
||||
&opts.source,
|
||||
&version_prefix,
|
||||
&opts.description,
|
||||
MigrationType::ReversibleDown,
|
||||
)?;
|
||||
} else {
|
||||
create_file(
|
||||
migration_source,
|
||||
file_prefix,
|
||||
description,
|
||||
&opts.source,
|
||||
&version_prefix,
|
||||
&opts.description,
|
||||
MigrationType::Simple,
|
||||
)?;
|
||||
}
|
||||
|
||||
// if the migrations directory is empty
|
||||
let has_existing_migrations = fs::read_dir(migration_source)
|
||||
let has_existing_migrations = fs::read_dir(&opts.source)
|
||||
.map(|mut dir| dir.next().is_some())
|
||||
.unwrap_or(false);
|
||||
|
||||
if !has_existing_migrations {
|
||||
let quoted_source = if migration_source != "migrations" {
|
||||
format!("{migration_source:?}")
|
||||
let quoted_source = if *opts.source != "migrations" {
|
||||
format!("{:?}", *opts.source)
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
use std::env;
|
||||
use std::ops::{Deref, Not};
|
||||
use std::path::Path;
|
||||
use anyhow::Context;
|
||||
use chrono::Utc;
|
||||
use clap::{Args, Parser};
|
||||
#[cfg(feature = "completions")]
|
||||
use clap_complete::Shell;
|
||||
use sqlx::config::Config;
|
||||
use crate::config::Config;
|
||||
use sqlx::migrate::Migrator;
|
||||
use crate::config::migrate::{DefaultMigrationType, DefaultVersioning};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version, about, author)]
|
||||
@ -125,8 +129,55 @@ pub struct MigrateOpt {
|
||||
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.
|
||||
///
|
||||
/// Reversible migrations are created as a pair of two files with the same filename but
|
||||
/// extensions `.up.sql` and `.down.sql` for the up-migration and down-migration, respectively.
|
||||
///
|
||||
/// The up-migration should contain the commands to be used when applying the migration,
|
||||
/// while the down-migration should contain the commands to reverse the changes made by the
|
||||
/// up-migration.
|
||||
///
|
||||
/// When writing down-migrations, care should be taken to ensure that they
|
||||
/// do not leave the database in an inconsistent state.
|
||||
///
|
||||
/// Simple migrations have just `.sql` for their extension and represent an up-migration only.
|
||||
///
|
||||
/// Note that reverting a migration is **destructive** and will likely result in data loss.
|
||||
/// Reverting a migration will not restore any data discarded by commands in the up-migration.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// Otherwise, a simple migration is created.
|
||||
///
|
||||
/// This behavior can be overridden by `--simple` or `--reversible`, respectively.
|
||||
///
|
||||
/// 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.
|
||||
/// Version numbers do not need to be strictly consecutive.
|
||||
///
|
||||
/// The migration process will abort if SQLx encounters a migration with a version number
|
||||
/// 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.
|
||||
///
|
||||
@ -136,28 +187,12 @@ pub enum MigrateCommand {
|
||||
///
|
||||
/// * only one migration exists and its version number is either 0 or 1.
|
||||
///
|
||||
/// Otherwise timestamp versioning is assumed.
|
||||
/// Otherwise, timestamp versioning (`YYYYMMDDHHMMSS`) is assumed.
|
||||
///
|
||||
/// This behavior can overridden by `--sequential` or `--timestamp`, respectively.
|
||||
Add {
|
||||
description: String,
|
||||
|
||||
#[clap(flatten)]
|
||||
source: Source,
|
||||
|
||||
/// If true, creates a pair of up and down migration files with same version
|
||||
/// else creates a single sql file
|
||||
#[clap(short)]
|
||||
reversible: bool,
|
||||
|
||||
/// If set, use timestamp versioning for the new migration. Conflicts with `--sequential`.
|
||||
#[clap(short, long)]
|
||||
timestamp: bool,
|
||||
|
||||
/// If set, use sequential versioning for the new migration. Conflicts with `--timestamp`.
|
||||
#[clap(short, long, conflicts_with = "timestamp")]
|
||||
sequential: bool,
|
||||
},
|
||||
/// This behavior can be overridden by `--timestamp` or `--sequential`, respectively.
|
||||
///
|
||||
/// The default versioning to use can also be set in `sqlx.toml`.
|
||||
Add(AddMigrationOpts),
|
||||
|
||||
/// Run all pending migrations.
|
||||
Run {
|
||||
@ -224,6 +259,34 @@ pub enum MigrateCommand {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct AddMigrationOpts {
|
||||
pub description: String,
|
||||
|
||||
#[clap(flatten)]
|
||||
pub source: Source,
|
||||
|
||||
/// If set, create an up-migration only. Conflicts with `--reversible`.
|
||||
#[clap(long, conflicts_with = "reversible")]
|
||||
simple: bool,
|
||||
|
||||
/// If set, create a pair of up and down migration files with same version.
|
||||
///
|
||||
/// Conflicts with `--simple`.
|
||||
#[clap(short, long, conflicts_with = "simple")]
|
||||
reversible: bool,
|
||||
|
||||
/// If set, use timestamp versioning for the new migration. Conflicts with `--sequential`.
|
||||
///
|
||||
/// Timestamp format: `YYYYMMDDHHMMSS`
|
||||
#[clap(short, long, conflicts_with = "sequential")]
|
||||
timestamp: bool,
|
||||
|
||||
/// If set, use sequential versioning for the new migration. Conflicts with `--timestamp`.
|
||||
#[clap(short, long, conflicts_with = "timestamp")]
|
||||
sequential: bool,
|
||||
}
|
||||
|
||||
/// Argument for the migration scripts source.
|
||||
#[derive(Args, Debug)]
|
||||
pub struct Source {
|
||||
@ -240,6 +303,12 @@ impl Deref for Source {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for Source {
|
||||
fn as_ref(&self) -> &Path {
|
||||
Path::new(&self.source)
|
||||
}
|
||||
}
|
||||
|
||||
/// Argument for the database URL.
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ConnectOpts {
|
||||
@ -338,3 +407,72 @@ impl Not for IgnoreMissing {
|
||||
!self.ignore_missing
|
||||
}
|
||||
}
|
||||
|
||||
impl AddMigrationOpts {
|
||||
pub fn reversible(&self, config: &Config, migrator: &Migrator) -> bool {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn version_prefix(&self, config: &Config, migrator: &Migrator) -> String {
|
||||
let default_versioning = &config.migrate.defaults.migration_versioning;
|
||||
|
||||
if self.timestamp || matches!(default_versioning, DefaultVersioning::Timestamp) {
|
||||
return next_timestamp();
|
||||
}
|
||||
|
||||
if self.sequential || matches!(default_versioning, DefaultVersioning::Sequential) {
|
||||
return next_sequential(migrator)
|
||||
.unwrap_or_else(|| fmt_sequential(1));
|
||||
}
|
||||
|
||||
next_sequential(migrator).unwrap_or_else(next_timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
fn next_timestamp() -> String {
|
||||
Utc::now().format("%Y%m%d%H%M%S").to_string()
|
||||
}
|
||||
|
||||
fn next_sequential(migrator: &Migrator) -> Option<String> {
|
||||
let next_version = migrator
|
||||
.migrations
|
||||
.windows(2)
|
||||
.last()
|
||||
.and_then(|migrations| {
|
||||
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] => {
|
||||
// If only one migration exists and its version is 0 or 1, infer sequential
|
||||
matches!(latest.version, 0 | 1)
|
||||
.then_some(latest.version + 1)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
|
||||
next_version.map(fmt_sequential)
|
||||
}
|
||||
|
||||
fn fmt_sequential(version: i64) -> String {
|
||||
format!("{version:04}")
|
||||
}
|
||||
|
||||
@ -74,8 +74,9 @@ impl MigrationType {
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated = "unused"]
|
||||
pub fn infer(migrator: &Migrator, reversible: bool) -> MigrationType {
|
||||
match migrator.iter().next() {
|
||||
match migrator.iter().last() {
|
||||
Some(first_migration) => first_migration.migration_type,
|
||||
None => {
|
||||
if reversible {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user