feat: introduce migrate.create-schemas

This commit is contained in:
Austin Bonander
2025-01-22 15:32:50 -08:00
parent 45c0b85b4c
commit 3765f67aba
17 changed files with 383 additions and 185 deletions

View File

@@ -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
}

View File

@@ -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")
}

View File

@@ -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"

View File

@@ -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)
}

View File

@@ -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,