mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-12-31 22:01:27 +00:00
* feat: create `sqlx.toml` format * feat: add support for ignored_chars config to sqlx_core::migrate * chore: test ignored_chars with `U+FEFF` (ZWNBSP/BOM) https://en.wikipedia.org/wiki/Byte_order_mark * refactor: make `Config` always compiled simplifies usage while still making parsing optional for less generated code * refactor: add origin information to `Column` * feat(macros): implement `type_override` and `column_override` from `sqlx.toml` * refactor(sqlx.toml): make all keys kebab-case, create `macros.preferred-crates` * feat: make macros aware of `macros.preferred-crates` * feat: make `sqlx-cli` aware of `database-url-var` * feat: teach macros about `migrate.table-name`, `migrations-dir` * feat: teach macros about `migrate.ignored-chars` * feat: teach `sqlx-cli` about `migrate.defaults` * feat: teach `sqlx-cli` about `migrate.migrations-dir` * feat: teach `sqlx-cli` about `migrate.table-name` * feat: introduce `migrate.create-schemas` * WIP feat: create multi-tenant database example * SQLite extension loading via sqlx.toml for CLI and query macros * fix: allow start_database to function when the SQLite database file does not already exist * Added example demonstrating migration and compile-time checking with SQLite extensions * remove accidentally included db file * Update sqlx-core/src/config/common.rs Doc formatting tweak Co-authored-by: Josh McKinney <joshka@users.noreply.github.com> * feat: create `sqlx.toml` format * feat: add support for ignored_chars config to sqlx_core::migrate * chore: test ignored_chars with `U+FEFF` (ZWNBSP/BOM) https://en.wikipedia.org/wiki/Byte_order_mark * refactor: make `Config` always compiled simplifies usage while still making parsing optional for less generated code * refactor: add origin information to `Column` * feat(macros): implement `type_override` and `column_override` from `sqlx.toml` * refactor(sqlx.toml): make all keys kebab-case, create `macros.preferred-crates` * feat: make macros aware of `macros.preferred-crates` * feat: make `sqlx-cli` aware of `database-url-var` * feat: teach macros about `migrate.table-name`, `migrations-dir` * feat: teach macros about `migrate.ignored-chars` * feat: teach `sqlx-cli` about `migrate.defaults` * feat: teach `sqlx-cli` about `migrate.migrations-dir` * feat: teach `sqlx-cli` about `migrate.table-name` * feat: introduce `migrate.create-schemas` * fix(postgres): don't fetch `ColumnOrigin` for transparently-prepared statements * feat: progress on axum-multi-tenant example * feat(config): better errors for mislabeled fields * WIP feat: filling out axum-multi-tenant example * feat: multi-tenant example No longer Axum-based because filling out the request routes would have distracted from the purpose of the example. * chore(ci): test multi-tenant example * fixup after merge * fix: CI, README for `multi-tenant` * fix: clippy warnings * fix: multi-tenant README * fix: sequential versioning inference for migrations * fix: migration versioning with explicit overrides * fix: only warn on ambiguous crates if the invocation relies on it * fix: remove unused imports * fix: `sqlx mig add` behavior and tests * fix: restore original type-checking order * fix: deprecation warning in `tests/postgres/macros.rs` * feat: create postgres/multi-database example * fix: examples/postgres/multi-database * fix: cargo fmt * chore: add tests for config `migrate.defaults` * fix: sqlx-cli/tests/add.rs * feat(cli): add `--config` override to all relevant commands * chore: run `sqlx mig add` test with `RUST_BACKTRACE=1` * fix: properly canonicalize config path for `sqlx mig add` test * fix: get `sqlx mig add` test passing * fix(cli): test `migrate.ignored-chars`, fix bugs * feat: create `macros.preferred-crates` example * fix(examples): use workspace `sqlx` * fix: examples * fix: run `cargo fmt` * fix: more example fixes * fix(ci): preferred-crates setup * fix: axum-multi-tenant example locked to specific sqlx version * import anyhow::Context trait in sqlx-cli/src/lib.rs since it was being used and causing a compile error * rebased on upstream/main * make cargo fmt happy * make clippy happy * make clippy happier still * fix: improved error reporting, added parsing test, removed sqlx-toml flag use * switched to kebab-case for the config key * switched to kebab-case for the config key --------- Co-authored-by: Austin Bonander <austin.bonander@gmail.com> Co-authored-by: Josh McKinney <joshka@users.noreply.github.com>
253 lines
7.6 KiB
Rust
253 lines
7.6 KiB
Rust
use std::future::Future;
|
|
use std::io;
|
|
use std::time::Duration;
|
|
|
|
use futures_util::TryFutureExt;
|
|
|
|
use sqlx::AnyConnection;
|
|
use tokio::{select, signal};
|
|
|
|
use crate::opt::{Command, ConnectOpts, DatabaseCommand, MigrateCommand};
|
|
|
|
mod database;
|
|
mod metadata;
|
|
// mod migration;
|
|
// mod migrator;
|
|
#[cfg(feature = "completions")]
|
|
mod completions;
|
|
mod migrate;
|
|
mod opt;
|
|
mod prepare;
|
|
|
|
pub use crate::opt::Opt;
|
|
|
|
pub use sqlx::_unstable::config::{self, Config};
|
|
|
|
/// Check arguments for `--no-dotenv` _before_ Clap parsing, and apply `.env` if not set.
|
|
pub fn maybe_apply_dotenv() {
|
|
if std::env::args().any(|arg| arg == "--no-dotenv") {
|
|
return;
|
|
}
|
|
|
|
dotenvy::dotenv().ok();
|
|
}
|
|
|
|
pub async fn run(opt: Opt) -> anyhow::Result<()> {
|
|
// This `select!` is here so that when the process receives a `SIGINT` (CTRL + C),
|
|
// the futures currently running on this task get dropped before the program exits.
|
|
// This is currently necessary for the consumers of the `dialoguer` crate to restore
|
|
// the user's terminal if the process is interrupted while a dialog is being displayed.
|
|
|
|
let ctrlc_fut = signal::ctrl_c();
|
|
let do_run_fut = do_run(opt);
|
|
|
|
select! {
|
|
biased;
|
|
_ = ctrlc_fut => {
|
|
Ok(())
|
|
},
|
|
do_run_outcome = do_run_fut => {
|
|
do_run_outcome
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn do_run(opt: Opt) -> anyhow::Result<()> {
|
|
match opt.command {
|
|
Command::Migrate(migrate) => match migrate.command {
|
|
MigrateCommand::Add(opts) => migrate::add(opts).await?,
|
|
MigrateCommand::Run {
|
|
source,
|
|
config,
|
|
dry_run,
|
|
ignore_missing,
|
|
mut connect_opts,
|
|
target_version,
|
|
} => {
|
|
let config = config.load_config().await?;
|
|
|
|
connect_opts.populate_db_url(&config)?;
|
|
|
|
migrate::run(
|
|
&config,
|
|
&source,
|
|
&connect_opts,
|
|
dry_run,
|
|
*ignore_missing,
|
|
target_version,
|
|
)
|
|
.await?
|
|
}
|
|
MigrateCommand::Revert {
|
|
source,
|
|
config,
|
|
dry_run,
|
|
ignore_missing,
|
|
mut connect_opts,
|
|
target_version,
|
|
} => {
|
|
let config = config.load_config().await?;
|
|
|
|
connect_opts.populate_db_url(&config)?;
|
|
|
|
migrate::revert(
|
|
&config,
|
|
&source,
|
|
&connect_opts,
|
|
dry_run,
|
|
*ignore_missing,
|
|
target_version,
|
|
)
|
|
.await?
|
|
}
|
|
MigrateCommand::Info {
|
|
source,
|
|
config,
|
|
mut connect_opts,
|
|
} => {
|
|
let config = config.load_config().await?;
|
|
|
|
connect_opts.populate_db_url(&config)?;
|
|
|
|
migrate::info(&config, &source, &connect_opts).await?
|
|
}
|
|
MigrateCommand::BuildScript {
|
|
source,
|
|
config,
|
|
force,
|
|
} => {
|
|
let config = config.load_config().await?;
|
|
|
|
migrate::build_script(&config, &source, force)?
|
|
}
|
|
},
|
|
|
|
Command::Database(database) => match database.command {
|
|
DatabaseCommand::Create {
|
|
config,
|
|
mut connect_opts,
|
|
} => {
|
|
let config = config.load_config().await?;
|
|
|
|
connect_opts.populate_db_url(&config)?;
|
|
database::create(&connect_opts).await?
|
|
}
|
|
DatabaseCommand::Drop {
|
|
confirmation,
|
|
config,
|
|
mut connect_opts,
|
|
force,
|
|
} => {
|
|
let config = config.load_config().await?;
|
|
|
|
connect_opts.populate_db_url(&config)?;
|
|
database::drop(&connect_opts, !confirmation.yes, force).await?
|
|
}
|
|
DatabaseCommand::Reset {
|
|
confirmation,
|
|
source,
|
|
config,
|
|
mut connect_opts,
|
|
force,
|
|
} => {
|
|
let config = config.load_config().await?;
|
|
|
|
connect_opts.populate_db_url(&config)?;
|
|
database::reset(&config, &source, &connect_opts, !confirmation.yes, force).await?
|
|
}
|
|
DatabaseCommand::Setup {
|
|
source,
|
|
config,
|
|
mut connect_opts,
|
|
} => {
|
|
let config = config.load_config().await?;
|
|
|
|
connect_opts.populate_db_url(&config)?;
|
|
database::setup(&config, &source, &connect_opts).await?
|
|
}
|
|
},
|
|
|
|
Command::Prepare {
|
|
check,
|
|
all,
|
|
workspace,
|
|
mut connect_opts,
|
|
args,
|
|
config,
|
|
} => {
|
|
let config = config.load_config().await?;
|
|
connect_opts.populate_db_url(&config)?;
|
|
prepare::run(check, all, workspace, connect_opts, args).await?
|
|
}
|
|
|
|
#[cfg(feature = "completions")]
|
|
Command::Completions { shell } => completions::run(shell),
|
|
};
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Attempt to connect to the database server, retrying up to `ops.connect_timeout`.
|
|
async fn connect(opts: &ConnectOpts) -> anyhow::Result<AnyConnection> {
|
|
retry_connect_errors(opts, move |url| {
|
|
// This only handles the default case. For good support of
|
|
// the new command line options, we need to work out some
|
|
// way to make the appropriate ConfigOpt available here. I
|
|
// suspect that that infrastructure would be useful for
|
|
// other things in the future, as well, but it also seems
|
|
// like an extensive and intrusive change.
|
|
//
|
|
// On the other hand, the compile-time checking macros
|
|
// can't be configured to use a different config file at
|
|
// all, so I believe this is okay for the time being.
|
|
let config = Some(std::path::PathBuf::from("sqlx.toml")).and_then(|p| {
|
|
if p.exists() {
|
|
Some(p)
|
|
} else {
|
|
None
|
|
}
|
|
});
|
|
|
|
async move { AnyConnection::connect_with_config(url, config.clone()).await }
|
|
})
|
|
.await
|
|
}
|
|
|
|
/// Attempt an operation that may return errors like `ConnectionRefused`,
|
|
/// retrying up until `ops.connect_timeout`.
|
|
///
|
|
/// The closure is passed `&ops.database_url` for easy composition.
|
|
async fn retry_connect_errors<'a, F, Fut, T>(
|
|
opts: &'a ConnectOpts,
|
|
mut connect: F,
|
|
) -> anyhow::Result<T>
|
|
where
|
|
F: FnMut(&'a str) -> Fut,
|
|
Fut: Future<Output = sqlx::Result<T>> + 'a,
|
|
{
|
|
let db_url = opts.expect_db_url()?;
|
|
|
|
backoff::future::retry(
|
|
backoff::ExponentialBackoffBuilder::new()
|
|
.with_max_elapsed_time(Some(Duration::from_secs(opts.connect_timeout)))
|
|
.build(),
|
|
|| {
|
|
connect(db_url).map_err(|e| -> backoff::Error<anyhow::Error> {
|
|
if let sqlx::Error::Io(ref ioe) = e {
|
|
match ioe.kind() {
|
|
io::ErrorKind::ConnectionRefused
|
|
| io::ErrorKind::ConnectionReset
|
|
| io::ErrorKind::ConnectionAborted => {
|
|
return backoff::Error::transient(e.into());
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
backoff::Error::permanent(e.into())
|
|
})
|
|
},
|
|
)
|
|
.await
|
|
}
|