1use crate::opt::{ConnectOpts, MigrationSourceOpt};
2use crate::{migrate, Config};
3use console::{style, Term};
4use dialoguer::Confirm;
5use sqlx::any::Any;
6use sqlx::migrate::MigrateDatabase;
7use std::{io, mem};
8use tokio::task;
9
10pub async fn create(connect_opts: &ConnectOpts) -> anyhow::Result<()> {
11 let exists = crate::retry_connect_errors(connect_opts, Any::database_exists).await?;
14
15 if !exists {
16 #[cfg(feature = "_sqlite")]
17 sqlx::sqlite::CREATE_DB_WAL.store(
18 connect_opts.sqlite_create_db_wal,
19 std::sync::atomic::Ordering::Release,
20 );
21
22 Any::create_database(connect_opts.expect_db_url()?).await?;
23 }
24
25 Ok(())
26}
27
28pub async fn drop(connect_opts: &ConnectOpts, confirm: bool, force: bool) -> anyhow::Result<()> {
29 if confirm && !ask_to_continue_drop(connect_opts.expect_db_url()?.to_owned()).await {
30 return Ok(());
31 }
32
33 let exists = crate::retry_connect_errors(connect_opts, Any::database_exists).await?;
36
37 if exists {
38 if force {
39 Any::force_drop_database(connect_opts.expect_db_url()?).await?;
40 } else {
41 Any::drop_database(connect_opts.expect_db_url()?).await?;
42 }
43 }
44
45 Ok(())
46}
47
48pub async fn reset(
49 config: &Config,
50 migration_source: &MigrationSourceOpt,
51 connect_opts: &ConnectOpts,
52 confirm: bool,
53 force: bool,
54) -> anyhow::Result<()> {
55 drop(connect_opts, confirm, force).await?;
56 setup(config, migration_source, connect_opts).await
57}
58
59pub async fn setup(
60 config: &Config,
61 migration_source: &MigrationSourceOpt,
62 connect_opts: &ConnectOpts,
63) -> anyhow::Result<()> {
64 create(connect_opts).await?;
65 migrate::run(config, migration_source, connect_opts, false, false, None).await
66}
67
68async fn ask_to_continue_drop(db_url: String) -> bool {
69 struct RestoreCursorGuard {
72 disarmed: bool,
73 }
74
75 impl Drop for RestoreCursorGuard {
76 fn drop(&mut self) {
77 if !self.disarmed {
78 Term::stderr().show_cursor().unwrap()
79 }
80 }
81 }
82
83 let mut guard = RestoreCursorGuard { disarmed: false };
84
85 let decision_result = task::spawn_blocking(move || {
86 Confirm::new()
87 .with_prompt(format!("Drop database at {}?", style(&db_url).cyan()))
88 .wait_for_newline(true)
89 .default(false)
90 .show_default(true)
91 .interact()
92 })
93 .await
94 .expect("Confirm thread panicked");
95 match decision_result {
96 Ok(decision) => {
97 guard.disarmed = true;
98 decision
99 }
100 Err(dialoguer::Error::IO(err)) if err.kind() == io::ErrorKind::Interrupted => {
101 mem::drop(guard);
103 false
104 }
105 Err(err) => {
106 mem::drop(guard);
107 panic!("Confirm dialog failed with {err}")
108 }
109 }
110}