sqlx_scylladb_cli/
opt.rs

1use std::ops::{Deref, Not};
2
3use clap::{
4    Args, Parser,
5    builder::{Styles, styling::AnsiColor},
6};
7
8const HELP_STYLES: Styles = Styles::styled()
9    .header(AnsiColor::Blue.on_default().bold())
10    .usage(AnsiColor::Blue.on_default().bold())
11    .literal(AnsiColor::White.on_default())
12    .placeholder(AnsiColor::Green.on_default());
13
14#[derive(Parser, Debug)]
15#[clap(version, about, author, styles = HELP_STYLES)]
16pub struct Opt {
17    // https://github.com/launchbadge/sqlx/pull/3724 placed this here,
18    // but the intuitive place would be in the arguments for each subcommand.
19    #[clap(flatten)]
20    pub no_dotenv: NoDotenvOpt,
21
22    #[clap(subcommand)]
23    pub command: Command,
24}
25
26#[derive(Parser, Debug)]
27pub enum Command {
28    #[clap(alias = "db")]
29    Database(DatabaseOpt),
30
31    #[clap(alias = "mig")]
32    Migrate(MigrateOpt),
33}
34
35/// Group of commands for creating and dropping your database.
36#[derive(Parser, Debug)]
37pub struct DatabaseOpt {
38    #[clap(subcommand)]
39    pub command: DatabaseCommand,
40}
41
42#[derive(Parser, Debug)]
43pub enum DatabaseCommand {
44    /// Creates the database specified in your DATABASE_URL.
45    Create {
46        #[clap(flatten)]
47        connect_opts: ConnectOpts,
48    },
49
50    /// Drops the database specified in your DATABASE_URL.
51    Drop {
52        #[clap(flatten)]
53        confirmation: Confirmation,
54
55        #[clap(flatten)]
56        connect_opts: ConnectOpts,
57
58        /// PostgreSQL only: force drops the database.
59        #[clap(long, short, default_value = "false")]
60        force: bool,
61    },
62
63    /// Drops the database specified in your DATABASE_URL, re-creates it, and runs any pending migrations.
64    Reset {
65        #[clap(flatten)]
66        confirmation: Confirmation,
67
68        #[clap(flatten)]
69        source: Source,
70
71        #[clap(flatten)]
72        connect_opts: ConnectOpts,
73
74        /// PostgreSQL only: force drops the database.
75        #[clap(long, short, default_value = "false")]
76        force: bool,
77    },
78
79    /// Creates the database specified in your DATABASE_URL and runs any pending migrations.
80    Setup {
81        #[clap(flatten)]
82        source: Source,
83
84        #[clap(flatten)]
85        connect_opts: ConnectOpts,
86    },
87}
88
89/// Group of commands for creating and running migrations.
90#[derive(Parser, Debug)]
91pub struct MigrateOpt {
92    #[clap(subcommand)]
93    pub command: MigrateCommand,
94}
95
96#[derive(Parser, Debug)]
97pub enum MigrateCommand {
98    /// Create a new migration with the given description.
99    ///
100    /// A version number will be automatically assigned to the migration.
101    ///
102    /// For convenience, this command will attempt to detect if sequential versioning is in use,
103    /// and if so, continue the sequence.
104    ///
105    /// Sequential versioning is inferred if:
106    ///
107    /// * The version numbers of the last two migrations differ by exactly 1, or:
108    ///
109    /// * only one migration exists and its version number is either 0 or 1.
110    ///
111    /// Otherwise timestamp versioning is assumed.
112    ///
113    /// This behavior can overridden by `--sequential` or `--timestamp`, respectively.
114    Add {
115        description: String,
116
117        #[clap(flatten)]
118        source: Source,
119
120        /// If true, creates a pair of up and down migration files with same version
121        /// else creates a single sql file
122        #[clap(short)]
123        reversible: bool,
124
125        /// If set, use timestamp versioning for the new migration. Conflicts with `--sequential`.
126        #[clap(short, long)]
127        timestamp: bool,
128
129        /// If set, use sequential versioning for the new migration. Conflicts with `--timestamp`.
130        #[clap(short, long, conflicts_with = "timestamp")]
131        sequential: bool,
132    },
133
134    /// Run all pending migrations.
135    Run {
136        #[clap(flatten)]
137        source: Source,
138
139        /// List all the migrations to be run without applying
140        #[clap(long)]
141        dry_run: bool,
142
143        #[clap(flatten)]
144        ignore_missing: IgnoreMissing,
145
146        #[clap(flatten)]
147        connect_opts: ConnectOpts,
148
149        /// Apply migrations up to the specified version. If unspecified, apply all
150        /// pending migrations. If already at the target version, then no-op.
151        #[clap(long)]
152        target_version: Option<i64>,
153    },
154
155    /// Revert the latest migration with a down file.
156    Revert {
157        #[clap(flatten)]
158        source: Source,
159
160        /// List the migration to be reverted without applying
161        #[clap(long)]
162        dry_run: bool,
163
164        #[clap(flatten)]
165        ignore_missing: IgnoreMissing,
166
167        #[clap(flatten)]
168        connect_opts: ConnectOpts,
169
170        /// Revert migrations down to the specified version. If unspecified, revert
171        /// only the last migration. Set to 0 to revert all migrations. If already
172        /// at the target version, then no-op.
173        #[clap(long)]
174        target_version: Option<i64>,
175    },
176
177    /// List all available migrations.
178    Info {
179        #[clap(flatten)]
180        source: Source,
181
182        #[clap(flatten)]
183        connect_opts: ConnectOpts,
184    },
185}
186
187/// Argument for the migration scripts source.
188#[derive(Args, Debug)]
189pub struct Source {
190    /// Path to folder containing migrations.
191    #[clap(long, default_value = "migrations")]
192    source: String,
193}
194
195impl Deref for Source {
196    type Target = String;
197
198    fn deref(&self) -> &Self::Target {
199        &self.source
200    }
201}
202
203/// Argument for the database URL.
204#[derive(Args, Debug)]
205pub struct ConnectOpts {
206    #[clap(flatten)]
207    pub no_dotenv: NoDotenvOpt,
208
209    /// Location of the DB, by default will be read from the DATABASE_URL env var or `.env` files.
210    #[clap(long, short = 'D', env)]
211    pub database_url: Option<String>,
212
213    /// Location of the DB, by default will be read from the SCYLLADB_URL env var or `.env` files.
214    #[clap(long, env)]
215    pub scylladb_url: Option<String>,
216
217    /// The maximum time, in seconds, to try connecting to the database server before
218    /// returning an error.
219    #[clap(long, default_value = "10")]
220    pub connect_timeout: u64,
221}
222
223#[derive(Args, Debug)]
224pub struct NoDotenvOpt {
225    /// Do not automatically load `.env` files.
226    #[clap(long)]
227    // Parsing of this flag is actually handled _before_ calling Clap,
228    // by `crate::maybe_apply_dotenv()`.
229    #[allow(unused)] // TODO: switch to `#[expect]`
230    pub no_dotenv: bool,
231}
232
233impl ConnectOpts {
234    /// Require a database URL to be provided, otherwise
235    /// return an error.
236    pub fn required_db_url(&self) -> anyhow::Result<&str> {
237        let database_url = if let Some(scylladb_url) = self.scylladb_url.as_deref() {
238            Some(scylladb_url)
239        } else {
240            self.database_url.as_deref()
241        };
242
243        database_url.ok_or_else(
244            || anyhow::anyhow!(
245                "the `--database-url` option or the `DATABASE_URL` or `SCYLLADB_URL` environment variable must be provided"
246            )
247        )
248    }
249}
250
251/// Argument for automatic confirmation.
252#[derive(Args, Copy, Clone, Debug)]
253pub struct Confirmation {
254    /// Automatic confirmation. Without this option, you will be prompted before dropping
255    /// your database.
256    #[clap(short)]
257    pub yes: bool,
258}
259
260/// Argument for ignoring applied migrations that were not resolved.
261#[derive(Args, Copy, Clone, Debug)]
262pub struct IgnoreMissing {
263    /// Ignore applied migrations that are missing in the resolved migrations
264    #[clap(long)]
265    ignore_missing: bool,
266}
267
268impl Deref for IgnoreMissing {
269    type Target = bool;
270
271    fn deref(&self) -> &Self::Target {
272        &self.ignore_missing
273    }
274}
275
276impl Not for IgnoreMissing {
277    type Output = bool;
278
279    fn not(self) -> Self::Output {
280        !self.ignore_missing
281    }
282}