mirror of
https://github.com/launchbadge/sqlx.git
synced 2026-03-20 17:14:02 +00:00
Merge remote-tracking branch 'origin/main' into sqlx-toml
# Conflicts: # Cargo.lock # Cargo.toml # sqlx-cli/src/database.rs # sqlx-cli/src/lib.rs # sqlx-mysql/src/connection/executor.rs
This commit is contained in:
@@ -26,7 +26,7 @@ path = "src/bin/cargo-sqlx.rs"
|
||||
|
||||
[dependencies]
|
||||
dotenvy = "0.15.0"
|
||||
tokio = { version = "1.15.0", features = ["macros", "rt", "rt-multi-thread"] }
|
||||
tokio = { version = "1.15.0", features = ["macros", "rt", "rt-multi-thread", "signal"] }
|
||||
sqlx = { workspace = true, default-features = false, features = [
|
||||
"runtime-tokio",
|
||||
"migrate",
|
||||
@@ -37,9 +37,8 @@ clap = { version = "4.3.10", features = ["derive", "env"] }
|
||||
clap_complete = { version = "4.3.1", optional = true }
|
||||
chrono = { version = "0.4.19", default-features = false, features = ["clock"] }
|
||||
anyhow = "1.0.52"
|
||||
async-trait = "0.1.52"
|
||||
console = "0.15.0"
|
||||
promptly = "0.3.0"
|
||||
dialoguer = { version = "0.11", default-features = false }
|
||||
serde_json = "1.0.73"
|
||||
glob = "0.3.0"
|
||||
openssl = { version = "0.10.38", optional = true }
|
||||
|
||||
@@ -13,9 +13,12 @@ enum Cli {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenvy::dotenv().ok();
|
||||
let Cli::Sqlx(opt) = Cli::parse();
|
||||
|
||||
if !opt.no_dotenv {
|
||||
dotenvy::dotenv().ok();
|
||||
}
|
||||
|
||||
if let Err(error) = sqlx_cli::run(opt).await {
|
||||
println!("{} {}", style("error:").bold().red(), error);
|
||||
process::exit(1);
|
||||
|
||||
@@ -4,9 +4,14 @@ use sqlx_cli::Opt;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenvy::dotenv().ok();
|
||||
let opt = Opt::parse();
|
||||
|
||||
if !opt.no_dotenv {
|
||||
dotenvy::dotenv().ok();
|
||||
}
|
||||
|
||||
// no special handling here
|
||||
if let Err(error) = sqlx_cli::run(Opt::parse()).await {
|
||||
if let Err(error) = sqlx_cli::run(opt).await {
|
||||
println!("{} {}", style("error:").bold().red(), error);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use crate::opt::{ConnectOpts, MigrationSourceOpt};
|
||||
use crate::{migrate, Config};
|
||||
use console::style;
|
||||
use promptly::{prompt, ReadlineError};
|
||||
use console::{style, Term};
|
||||
use dialoguer::Confirm;
|
||||
use sqlx::any::Any;
|
||||
use sqlx::migrate::MigrateDatabase;
|
||||
use std::{io, mem};
|
||||
use tokio::task;
|
||||
|
||||
pub async fn create(connect_opts: &ConnectOpts) -> anyhow::Result<()> {
|
||||
// NOTE: only retry the idempotent action.
|
||||
@@ -24,7 +26,7 @@ pub async fn create(connect_opts: &ConnectOpts) -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
pub async fn drop(connect_opts: &ConnectOpts, confirm: bool, force: bool) -> anyhow::Result<()> {
|
||||
if confirm && !ask_to_continue_drop(connect_opts.expect_db_url()?) {
|
||||
if confirm && !ask_to_continue_drop(connect_opts.expect_db_url()?.to_owned()).await {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -63,27 +65,46 @@ pub async fn setup(
|
||||
migrate::run(config, migration_source, connect_opts, false, false, None).await
|
||||
}
|
||||
|
||||
fn ask_to_continue_drop(db_url: &str) -> bool {
|
||||
loop {
|
||||
let r: Result<String, ReadlineError> =
|
||||
prompt(format!("Drop database at {}? (y/n)", style(db_url).cyan()));
|
||||
match r {
|
||||
Ok(response) => {
|
||||
if response == "n" || response == "N" {
|
||||
return false;
|
||||
} else if response == "y" || response == "Y" {
|
||||
return true;
|
||||
} else {
|
||||
println!(
|
||||
"Response not recognized: {}\nPlease type 'y' or 'n' and press enter.",
|
||||
response
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{e}");
|
||||
return false;
|
||||
async fn ask_to_continue_drop(db_url: String) -> bool {
|
||||
// If the setup operation is cancelled while we are waiting for the user to decide whether
|
||||
// or not to drop the database, this will restore the terminal's cursor to its normal state.
|
||||
struct RestoreCursorGuard {
|
||||
disarmed: bool,
|
||||
}
|
||||
|
||||
impl Drop for RestoreCursorGuard {
|
||||
fn drop(&mut self) {
|
||||
if !self.disarmed {
|
||||
Term::stderr().show_cursor().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut guard = RestoreCursorGuard { disarmed: false };
|
||||
|
||||
let decision_result = task::spawn_blocking(move || {
|
||||
Confirm::new()
|
||||
.with_prompt(format!("Drop database at {}?", style(&db_url).cyan()))
|
||||
.wait_for_newline(true)
|
||||
.default(false)
|
||||
.show_default(true)
|
||||
.interact()
|
||||
})
|
||||
.await
|
||||
.expect("Confirm thread panicked");
|
||||
match decision_result {
|
||||
Ok(decision) => {
|
||||
guard.disarmed = true;
|
||||
decision
|
||||
}
|
||||
Err(dialoguer::Error::IO(err)) if err.kind() == io::ErrorKind::Interrupted => {
|
||||
// Sometimes CTRL + C causes this error to be returned
|
||||
mem::drop(guard);
|
||||
false
|
||||
}
|
||||
Err(err) => {
|
||||
mem::drop(guard);
|
||||
panic!("Confirm dialog failed with {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use anyhow::{Context, Result};
|
||||
use futures::{Future, TryFutureExt};
|
||||
|
||||
use sqlx::{AnyConnection, Connection};
|
||||
use tokio::{select, signal};
|
||||
|
||||
use crate::opt::{Command, ConnectOpts, DatabaseCommand, MigrateCommand};
|
||||
|
||||
@@ -24,6 +25,26 @@ pub use crate::opt::Opt;
|
||||
pub use sqlx::_unstable::config::{self, Config};
|
||||
|
||||
pub async fn run(opt: Opt) -> 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) -> Result<()> {
|
||||
let config = config_from_current_dir().await?;
|
||||
|
||||
match opt.command {
|
||||
|
||||
@@ -12,6 +12,10 @@ use std::ops::{Deref, Not};
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version, about, author)]
|
||||
pub struct Opt {
|
||||
/// Do not automatically load `.env` files.
|
||||
#[clap(long)]
|
||||
pub no_dotenv: bool,
|
||||
|
||||
#[clap(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user