1use clap::{Args, Parser, Subcommand, ValueEnum};
7use std::path::PathBuf;
8
9use crate::cli::{ExportFormat, ImportFormat, InfoOutputFormat, OutputFormat};
10
11#[derive(Debug, Clone, Copy, ValueEnum)]
14pub enum OutputMode {
15 Table,
16 Json,
17 Csv,
18 Parquet,
20}
21
22impl OutputMode {
23 #[allow(dead_code)]
25 pub fn as_str(&self) -> &'static str {
26 match self {
27 OutputMode::Table => "table",
28 OutputMode::Json => "json",
29 OutputMode::Csv => "csv",
30 OutputMode::Parquet => "parquet",
31 }
32 }
33}
34
35#[derive(Parser)]
36#[command(name = "cqlite")]
37#[command(about = "CQLite - Local SSTable query tool with cqlsh-compatible interface")]
38#[command(
39 long_about = "CQLite provides cqlsh-compatible access to Apache Cassandra 5.0 SSTables locally without cluster dependencies. Supports interactive REPL and one-shot query modes.\n\nIngestion Model: Provide --schema and --data-dir together to trigger schema loading + dataset discovery for query execution.\n\nNote: Timestamps are displayed in UTC for M2 milestone."
40)]
41#[command(version = env!("CARGO_PKG_VERSION"))]
42#[command(author = "CQLite Team")]
43pub struct Cli {
44 #[arg(short, long, value_name = "FILE")]
46 pub database: Option<PathBuf>,
47
48 #[arg(short, long, value_name = "FILE")]
50 pub config: Option<PathBuf>,
51
52 #[arg(short, long, action = clap::ArgAction::Count)]
54 pub verbose: u8,
55
56 #[arg(short, long)]
58 pub quiet: bool,
59
60 #[arg(long, value_enum, default_value = "table")]
62 pub format: OutputFormat,
63
64 #[arg(long)]
66 pub auto_detect: bool,
67
68 #[arg(long, value_name = "VER")]
70 pub cassandra_version: Option<String>,
71
72 #[arg(long, value_name = "PATH", env = "CQLITE_SCHEMA")]
76 pub schema: Option<PathBuf>,
77
78 #[arg(long, value_name = "DATASET", conflicts_with = "data_dir")]
81 pub dataset: Option<String>,
82
83 #[arg(
87 long,
88 value_name = "DIR",
89 env = "CQLITE_DATA_DIR",
90 conflicts_with = "dataset"
91 )]
92 pub data_dir: Option<PathBuf>,
93
94 #[arg(short = 'e', long, visible_alias = "query", value_name = "CQL")]
96 pub execute: Option<String>,
97
98 #[arg(short = 'f', long, value_name = "CQL_FILE")]
100 pub file: Option<PathBuf>,
101
102 #[arg(long, value_enum, env = "CQLITE_OUT")]
106 pub out: Option<OutputMode>,
107
108 #[arg(short = 'o', long, value_name = "FILE", env = "CQLITE_OUTPUT")]
111 pub output: Option<PathBuf>,
112
113 #[arg(long, requires = "output")]
115 pub overwrite: bool,
116
117 #[arg(long, value_name = "N", env = "CQLITE_LIMIT")]
119 pub limit: Option<usize>,
120
121 #[arg(long, value_name = "N", env = "CQLITE_PAGE_SIZE")]
123 pub page_size: Option<usize>,
124
125 #[arg(long, env = "CQLITE_NO_COLOR")]
127 pub no_color: bool,
128
129 #[arg(long, env = "CQLITE_ENABLE_SELECT_FALLBACK")]
131 pub enable_select_fallback: bool,
132
133 #[arg(long, env = "CQLITE_WRITABLE", requires = "write_dir")]
135 pub writable: bool,
136
137 #[arg(long, value_name = "DIR", env = "CQLITE_WRITE_DIR")]
139 pub write_dir: Option<PathBuf>,
140
141 #[arg(long, value_name = "JSON", requires = "writable")]
144 pub mutation: Vec<String>,
145
146 #[arg(long, value_name = "FILE", requires = "writable")]
148 pub mutations_file: Option<PathBuf>,
149
150 #[arg(long, requires = "writable")]
152 pub flush: bool,
153
154 #[command(subcommand)]
155 pub command: Option<Commands>,
156}
157
158#[derive(Subcommand)]
159pub enum Commands {
160 #[command(
162 long_about = "Interactive REPL supporting meta-commands (:config, :schema, :status, :health) and CQL queries (SELECT, DESCRIBE, USE). This is a basic text-based REPL with status line. For the full terminal UI, use 'cqlite tui'."
163 )]
164 Repl,
165 #[command(
167 long_about = "Full-screen terminal UI with panels for tables, query results, and history. Navigate with Tab, toggle panels with F2-F4, reset layout with F5. Exit with Ctrl+C or Esc. For basic REPL mode, use 'cqlite repl'."
168 )]
169 Tui,
170 #[command(
172 long_about = "Friendly wrapper for one-shot query execution. Example: cqlite query --schema schemas/ --data-dir ./test-data -e \"SELECT * FROM ks.users LIMIT 5\" --out json"
173 )]
174 Query {
175 query: String,
177 #[arg(long)]
179 explain: bool,
180 #[arg(long)]
182 timing: bool,
183 },
184 Import {
186 file: PathBuf,
188 #[arg(short, long, value_enum, default_value = "csv")]
190 format: ImportFormat,
191 #[arg(short, long)]
193 table: String,
194 #[arg(short, long)]
196 mapping: Option<String>,
197 #[arg(long, default_value = "1000")]
199 batch_size: usize,
200 },
201 Export {
203 file: PathBuf,
205 #[arg(short, long, value_enum, default_value = "csv")]
207 format: ExportFormat,
208 #[arg(short, long)]
210 table: String,
211 #[arg(short, long)]
213 query: Option<String>,
214 #[arg(short, long)]
216 limit: Option<usize>,
217 },
218 Admin {
220 #[command(subcommand)]
221 command: AdminCommands,
222 },
223 Schema {
225 #[command(subcommand)]
226 command: SchemaCommands,
227 },
228 Bench {
230 #[command(subcommand)]
231 command: BenchCommands,
232 },
233 #[command(name = "read-sstable")]
235 #[command(
236 long_about = "Direct SSTable inspection bypassing schema. Example: cqlite read-sstable ./test-data/datasets/sstables/test_basic/simple_table/na-1-big-Data.db --schema schema.cql --format table"
237 )]
238 ReadSstable {
239 file: PathBuf,
241 #[arg(short, long, value_enum, default_value = "table")]
243 format: OutputFormat,
244 #[arg(short, long)]
246 limit: Option<usize>,
247 #[arg(short, long, default_value = "0")]
249 skip: usize,
250 #[arg(long)]
252 keys_only: bool,
253 #[arg(long)]
255 raw: bool,
256 #[arg(long)]
258 verbose: bool,
259 },
260 #[command(
262 long_about = "Show SSTable or database file metadata, stats, and optional validation. Example: cqlite info ./test-data/datasets/sstables/test_basic --format json --detailed"
263 )]
264 Info {
265 path: Option<PathBuf>,
267 #[arg(short, long, value_enum, default_value = "text")]
269 format: InfoOutputFormat,
270 #[arg(short, long)]
272 detailed: bool,
273 },
274 #[command(
276 long_about = "Perform incremental background compaction work within a time budget. Call repeatedly from a background task for continuous compaction. Example: cqlite maintenance --budget-ms 100 --writable --write-dir /path/to/data"
277 )]
278 Maintenance(MaintenanceArgs),
279 #[command(name = "write-stats")]
281 #[command(
282 long_about = "Show current write engine statistics including memtable size, row count, WAL size, and generation number. Example: cqlite write-stats --writable --write-dir /path/to/data"
283 )]
284 WriteStats,
285 #[command(name = "export-sstable")]
287 #[command(
288 long_about = "Export data from the write engine as Cassandra-compatible SSTables. Use --compact to run compaction before export to merge multiple SSTables into one. Example: cqlite export-sstable /tmp/export --compact --writable --write-dir /path/to/data"
289 )]
290 ExportSstable(ExportSstableArgs),
291}
292
293#[derive(Subcommand)]
294pub enum AdminCommands {
295 Info,
297 Compact {
299 #[arg(long)]
301 force: bool,
302 },
303 Backup {
305 destination: PathBuf,
307 #[arg(long, default_value = "6")]
309 compression: u8,
310 },
311 Restore {
313 backup: PathBuf,
315 #[arg(long)]
317 force: bool,
318 },
319}
320
321#[derive(Subcommand)]
322pub enum SchemaCommands {
323 List,
325 Describe {
327 table: String,
329 },
330 Create {
332 schema: PathBuf,
334 },
335 Drop {
337 table: String,
339 #[arg(long)]
341 force: bool,
342 },
343 Load {
345 #[arg(required = true)]
347 paths: Vec<PathBuf>,
348 },
349}
350
351#[derive(Subcommand)]
352pub enum BenchCommands {
353 Read {
355 #[arg(short, long, default_value = "10000")]
357 operations: usize,
358 #[arg(short, long, default_value = "1")]
360 concurrency: usize,
361 #[arg(short, long)]
363 table: Option<String>,
364 },
365 Write {
367 #[arg(short, long, default_value = "10000")]
369 operations: usize,
370 #[arg(short, long, default_value = "1")]
372 concurrency: usize,
373 #[arg(short, long)]
375 table: Option<String>,
376 },
377 Mixed {
379 #[arg(short, long, default_value = "10000")]
381 operations: usize,
382 #[arg(long, default_value = "0.8")]
384 read_ratio: f64,
385 #[arg(short, long, default_value = "1")]
387 concurrency: usize,
388 },
389}
390
391#[derive(Args, Debug, Clone)]
393pub struct MaintenanceArgs {
394 #[arg(long, default_value = "100")]
396 pub budget_ms: u64,
397}
398
399#[derive(Args, Debug, Clone)]
401pub struct ExportSstableArgs {
402 pub output: PathBuf,
404 #[arg(long, default_value = "export")]
406 pub keyspace: String,
407 #[arg(long, default_value = "data")]
409 pub table: String,
410 #[arg(long)]
412 pub compact: bool,
413 #[arg(long)]
415 pub skip_validate: bool,
416}