icydb-cli 0.162.6

Developer CLI tools for IcyDB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
//! Module: CLI argument surface.
//! Responsibility: define clap parsing structs and stable accessors for command inputs.
//! Does not own: command execution, config resolution, or rendered output.
//! Boundary: exposes parsed command values to the command dispatcher and owner modules.

use std::path::{Path, PathBuf};

use clap::{ArgAction, Args, Parser, Subcommand};

pub(crate) const DEFAULT_ENVIRONMENT: &str = "demo";

///
/// CliArgs
///
/// CliArgs owns the top-level process argument surface for the developer CLI.
/// The initial keyword selects a functional family so SQL execution and
/// canister lifecycle operations do not share one flag namespace.
///

#[derive(Debug, Parser)]
#[command(
    name = "icydb",
    about = "Developer CLI tools for IcyDB",
    long_about = None
)]
pub(crate) struct CliArgs {
    #[command(subcommand)]
    command: CliCommand,
}

impl CliArgs {
    pub(crate) fn into_command(self) -> CliCommand {
        self.command
    }
}

///
/// CliCommand
///
/// CliCommand is the top-level functional-family dispatch for the developer
/// CLI. Each variant owns one user-facing keyword so future command growth can
/// stay grouped by intent instead of growing one shared flag bag.
///

#[derive(Debug, Subcommand)]
pub(crate) enum CliCommand {
    /// Run SQL against an IcyDB canister.
    Sql(SqlArgs),

    /// Read storage inventory from an IcyDB canister.
    Snapshot(CanisterTarget),

    /// Read or reset metrics on an IcyDB canister.
    Metrics(MetricsArgs),

    /// Inspect accepted and generated schema metadata from an IcyDB canister.
    #[command(subcommand)]
    Schema(SchemaCommand),

    /// Inspect and validate IcyDB TOML config.
    #[command(subcommand)]
    Config(ConfigCommand),

    /// Manage a local ICP canister.
    #[command(subcommand)]
    Canister(CanisterCommand),
}

///
/// SqlArgs
///
/// SqlArgs owns the SQL shell command surface. It preserves the interactive
/// shell, explicit `--sql`, ICP environment defaults, and trailing SQL
/// convenience form while keeping SQL-specific flags under the `sql` keyword.
///

#[derive(Args, Debug)]
#[command(
    trailing_var_arg = true,
    after_help = "Examples:
  icydb sql -c demo_rpg
  icydb sql -c demo_rpg --sql \"SELECT name FROM character LIMIT 5\"
  icydb sql -c demo_rpg --sql \"CREATE INDEX character_renown_idx ON character (renown)\"
  icydb sql -c demo_rpg --sql \"DROP INDEX character_renown_idx ON character\""
)]
pub(crate) struct SqlArgs {
    /// Target ICP canister name.
    #[arg(short, long, required = true)]
    canister: String,

    /// Target icp-cli environment.
    #[arg(short, long, env = "ICP_ENVIRONMENT", default_value = DEFAULT_ENVIRONMENT)]
    environment: String,

    /// Interactive shell history file.
    #[arg(long, default_value = ".cache/sql_history")]
    history_file: PathBuf,

    /// Execute one SQL statement, including supported DDL, and exit.
    #[arg(long, conflicts_with = "trailing_sql")]
    sql: Option<String>,

    /// SQL statement, including supported DDL, passed without --sql.
    #[arg(value_name = "SQL", allow_hyphen_values = true)]
    trailing_sql: Vec<String>,
}

impl SqlArgs {
    pub(crate) fn into_shell_fields(
        self,
    ) -> (String, String, PathBuf, Option<String>, Vec<String>) {
        (
            self.canister,
            self.environment,
            self.history_file,
            self.sql,
            self.trailing_sql,
        )
    }
}

///
/// CanisterTarget
///
/// CanisterTarget is the shared target selector for icp-cli-backed commands. It
/// keeps explicit canister selection and the environment override consistent
/// across lifecycle commands.
///

#[derive(Args, Clone, Debug)]
pub(crate) struct CanisterTarget {
    /// Target ICP canister name.
    #[arg(value_name = "CANISTER")]
    canister: String,

    /// Target icp-cli environment.
    #[arg(short, long, env = "ICP_ENVIRONMENT", default_value = DEFAULT_ENVIRONMENT)]
    environment: String,
}

impl CanisterTarget {
    pub(crate) const fn canister_name(&self) -> &str {
        self.canister.as_str()
    }

    pub(crate) const fn environment(&self) -> &str {
        self.environment.as_str()
    }
}

///
/// SchemaCommand
///
/// SchemaCommand owns live schema observability. `show` reads the accepted
/// schema report; `check` compares the generated proposal compiled into the
/// deployed canister with the accepted runtime catalog.
///

#[derive(Debug, Subcommand)]
pub(crate) enum SchemaCommand {
    /// Read accepted schema metadata from an IcyDB canister.
    Show(CanisterTarget),
    /// Compare generated schema metadata with accepted live schema metadata.
    Check(CanisterTarget),
}

#[derive(Args, Clone, Debug)]
pub(crate) struct EnvironmentTarget {
    /// Target icp-cli environment.
    #[arg(short, long, env = "ICP_ENVIRONMENT", default_value = DEFAULT_ENVIRONMENT)]
    environment: String,
}

impl EnvironmentTarget {
    pub(crate) const fn environment(&self) -> &str {
        self.environment.as_str()
    }
}

///
/// MetricsArgs
///
/// MetricsArgs owns the generated metrics endpoint command surface. The reset
/// switch keeps normal read usage short while still making the destructive
/// operation explicit.
///

#[derive(Args, Debug)]
pub(crate) struct MetricsArgs {
    #[command(flatten)]
    target: CanisterTarget,

    /// Only include metrics windows starting at this millisecond timestamp.
    #[arg(long, conflicts_with = "reset")]
    window_start_ms: Option<u64>,

    /// Reset in-memory metrics instead of reading the metrics report.
    #[arg(long)]
    reset: bool,
}

impl MetricsArgs {
    pub(crate) const fn target(&self) -> &CanisterTarget {
        &self.target
    }

    pub(crate) const fn window_start_ms(&self) -> Option<u64> {
        self.window_start_ms
    }

    pub(crate) const fn reset(&self) -> bool {
        self.reset
    }
}

///
/// ConfigCommand
///
/// ConfigCommand owns creation and inspection of `icydb.toml`. Inspection can
/// optionally compare configured canister surfaces against an explicit ICP
/// environment.
///

#[derive(Debug, Subcommand)]
pub(crate) enum ConfigCommand {
    /// Create a default IcyDB config file.
    Init(ConfigInitArgs),
    /// Show resolved IcyDB config, optionally comparing it with an ICP environment.
    Show(ConfigArgs),
    /// Validate resolved IcyDB config and optionally check an ICP environment.
    Check(ConfigArgs),
}

///
/// ConfigArgs
///
/// ConfigArgs carries the read-only config resolver inputs. `start_dir`
/// defaults to the current working directory; pass a canister crate directory
/// to inspect the same nearest-ancestor config that build scripts consume.
///

#[derive(Args, Clone, Debug)]
pub(crate) struct ConfigArgs {
    /// Directory to start nearest `icydb.toml` discovery from.
    #[arg(long)]
    start_dir: Option<PathBuf>,

    /// Optional icp-cli environment used for sync checks.
    #[arg(short, long, env = "ICP_ENVIRONMENT")]
    environment: Option<String>,
}

impl ConfigArgs {
    pub(crate) fn environment(&self) -> Option<&str> {
        self.environment.as_deref()
    }

    pub(crate) fn start_dir(&self) -> Option<&Path> {
        self.start_dir.as_deref()
    }
}

///
/// ConfigInitArgs
///
/// ConfigInitArgs carries the inputs for creating a new DB-surface config.
/// It writes to the workspace root when one is visible from `start_dir`,
/// otherwise to `start_dir` itself.
///

#[derive(Args, Clone, Debug)]
#[allow(
    clippy::struct_excessive_bools,
    reason = "clap flag bags intentionally mirror independent command-line switches"
)]
pub(crate) struct ConfigInitArgs {
    /// Directory used to choose where `icydb.toml` should be written.
    #[arg(long)]
    start_dir: Option<PathBuf>,

    /// Canister whose generated DB endpoint surfaces should be configured.
    #[arg(short, long, required = true)]
    canister: String,

    /// Also generate the DDL endpoint.
    #[arg(long)]
    ddl: bool,

    /// Also generate fixture lifecycle endpoints.
    #[arg(long)]
    fixtures: bool,

    /// Also generate metrics report endpoint.
    #[arg(long)]
    metrics: bool,

    /// Also generate the metrics reset endpoint.
    #[arg(long = "metrics-reset")]
    metrics_reset: bool,

    /// Also generate storage snapshot endpoint.
    #[arg(long)]
    snapshot: bool,

    /// Also generate accepted schema report endpoint.
    #[arg(long)]
    schema: bool,

    /// Generate all currently supported DB endpoint families.
    #[arg(long)]
    all: bool,

    /// Disable the default readonly SQL endpoint.
    #[arg(long = "no-readonly", action = ArgAction::SetFalse, default_value_t = true)]
    readonly: bool,

    /// Replace an existing target config file.
    #[arg(long)]
    force: bool,
}

impl ConfigInitArgs {
    pub(crate) fn start_dir(&self) -> Option<&Path> {
        self.start_dir.as_deref()
    }

    pub(crate) const fn canister_name(&self) -> &str {
        self.canister.as_str()
    }

    pub(crate) const fn readonly(&self) -> bool {
        self.readonly
    }

    pub(crate) const fn ddl(&self) -> bool {
        self.ddl || self.all
    }

    pub(crate) const fn fixtures(&self) -> bool {
        self.fixtures || self.all
    }

    pub(crate) const fn metrics(&self) -> bool {
        self.metrics || self.all
    }

    pub(crate) const fn metrics_reset(&self) -> bool {
        self.metrics_reset || self.all
    }

    pub(crate) const fn snapshot(&self) -> bool {
        self.snapshot || self.all
    }

    pub(crate) const fn schema(&self) -> bool {
        self.schema || self.all
    }

    pub(crate) const fn force(&self) -> bool {
        self.force
    }
}

///
/// CanisterCommand
///
/// CanisterCommand owns local canister lifecycle operations that were formerly
/// exposed as SQL shell flags. The subcommands mirror icp-cli operations closely
/// so lifecycle effects stay explicit.
///

#[derive(Debug, Subcommand)]
pub(crate) enum CanisterCommand {
    /// List known local IcyDB canisters and whether icp-cli has an id for them.
    List(EnvironmentTarget),
    /// Deploy the canister, preserving stable memory on existing installs.
    Deploy(CanisterTarget),
    /// Refresh the selected ICP canister and reload fixtures when available.
    Refresh(CanisterTarget),
    /// Build and upgrade the canister without resetting stable memory.
    Upgrade(UpgradeArgs),
    /// Show icp-cli status for the selected canister.
    Status(CanisterTarget),
}

///
/// UpgradeArgs
///
/// UpgradeArgs carries the local canister upgrade inputs. The optional wasm
/// override supports advanced flows while the default path preserves the
/// previous local SQL helper upgrade behavior.
///

#[derive(Args, Debug)]
pub(crate) struct UpgradeArgs {
    #[command(flatten)]
    target: CanisterTarget,

    /// Wasm path to install after build.
    #[arg(long)]
    wasm: Option<PathBuf>,
}

impl UpgradeArgs {
    pub(crate) const fn target(&self) -> &CanisterTarget {
        &self.target
    }

    pub(crate) fn wasm(&self) -> Option<&Path> {
        self.wasm.as_deref()
    }
}