Skip to main content

cqlite_cli/
cli_types.rs

1//! CLI type definitions
2//!
3//! This module contains all the CLI command structures and enums
4//! that are used throughout the application.
5
6use clap::{Args, Parser, Subcommand, ValueEnum};
7use std::path::PathBuf;
8
9use crate::cli::{ExportFormat, ImportFormat, InfoOutputFormat, OutputFormat};
10
11/// Output mode for query results (distinct from display format)
12/// Mirrors OutputFormat variants for consistency with --format flag.
13#[derive(Debug, Clone, Copy, ValueEnum)]
14pub enum OutputMode {
15    Table,
16    Json,
17    Csv,
18    /// Parquet binary format (requires --output flag)
19    Parquet,
20}
21
22impl OutputMode {
23    /// Convert OutputMode to its string representation
24    #[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    /// Database file path
45    #[arg(short, long, value_name = "FILE")]
46    pub database: Option<PathBuf>,
47
48    /// Load config (TOML/YAML/JSON). Precedence: flags > env > file > defaults
49    #[arg(short, long, value_name = "FILE")]
50    pub config: Option<PathBuf>,
51
52    /// Verbose output (-v, -vv, -vvv for increasing verbosity)
53    #[arg(short, long, action = clap::ArgAction::Count)]
54    pub verbose: u8,
55
56    /// Quiet mode (suppress non-essential output)
57    #[arg(short, long)]
58    pub quiet: bool,
59
60    /// Output format
61    #[arg(long, value_enum, default_value = "table")]
62    pub format: OutputFormat,
63
64    /// Enable best-effort auto detection (format/version) when available
65    #[arg(long)]
66    pub auto_detect: bool,
67
68    /// Hint (e.g., 5.0) for format compatibility
69    #[arg(long, value_name = "VER")]
70    pub cassandra_version: Option<String>,
71
72    /// CQL (.cql) or JSON (.json) schema files; triggers schema loading for ingestion.
73    /// When combined with --data-dir, enables schema-aware query execution.
74    /// Repeatable; order defines precedence
75    #[arg(long, value_name = "PATH", env = "CQLITE_SCHEMA")]
76    pub schema: Option<PathBuf>,
77
78    /// Dataset name for test data (e.g., test_basic, test_collections)
79    /// Mutually exclusive with --data-dir. Looks for datasets in CQLITE_DATASETS_ROOT/sstables/{dataset}/
80    #[arg(long, value_name = "DATASET", conflicts_with = "data_dir")]
81    pub dataset: Option<String>,
82
83    /// Cassandra data directory root (e.g., /var/lib/cassandra/data).
84    /// Combined with --schema, triggers dataset discovery and ingestion.
85    /// Mutually exclusive with --dataset. For production Cassandra directory layouts.
86    #[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    /// Execute a single CQL statement in one-shot mode (alias: --query)
95    #[arg(short = 'e', long, visible_alias = "query", value_name = "CQL")]
96    pub execute: Option<String>,
97
98    /// Execute statements from a file (semicolon-terminated)
99    #[arg(short = 'f', long, value_name = "CQL_FILE")]
100    pub file: Option<PathBuf>,
101
102    /// Output format for query results. Takes precedence over --format when specified.
103    /// Applies to: --execute/-e, --query, --file, and query subcommand.
104    /// (table = cqlsh-compatible format)
105    #[arg(long, value_enum, env = "CQLITE_OUT")]
106    pub out: Option<OutputMode>,
107
108    /// Output file path for query results. If not specified, output goes to stdout.
109    /// Required when format is 'parquet' (binary format cannot be written to stdout).
110    #[arg(short = 'o', long, value_name = "FILE", env = "CQLITE_OUTPUT")]
111    pub output: Option<PathBuf>,
112
113    /// Overwrite output file if it exists (default: error if file exists)
114    #[arg(long, requires = "output")]
115    pub overwrite: bool,
116
117    /// Cap rows
118    #[arg(long, value_name = "N", env = "CQLITE_LIMIT")]
119    pub limit: Option<usize>,
120
121    /// Reader and display pagination size
122    #[arg(long, value_name = "N", env = "CQLITE_PAGE_SIZE")]
123    pub page_size: Option<usize>,
124
125    /// Disable colored output
126    #[arg(long, env = "CQLITE_NO_COLOR")]
127    pub no_color: bool,
128
129    /// EXPERIMENTAL: Fallback to read-sstable for SELECT when ingestion unavailable (temporary, will be removed in M3)
130    #[arg(long, env = "CQLITE_ENABLE_SELECT_FALLBACK")]
131    pub enable_select_fallback: bool,
132
133    /// Enable write mode (requires --write-dir)
134    #[arg(long, env = "CQLITE_WRITABLE", requires = "write_dir")]
135    pub writable: bool,
136
137    /// Directory for write operations (WAL and SSTable output)
138    #[arg(long, value_name = "DIR", env = "CQLITE_WRITE_DIR")]
139    pub write_dir: Option<PathBuf>,
140
141    /// JSON mutation to write (can be repeated). Requires --writable mode.
142    /// Format: {"table":"ks.tbl","partition_key":{"col":"val"},"operations":[{"Write":{"column":"c","value":"v"}}],"timestamp_micros":123}
143    #[arg(long, value_name = "JSON", requires = "writable")]
144    pub mutation: Vec<String>,
145
146    /// File containing mutations (JSONL format, one JSON mutation per line). Requires --writable mode.
147    #[arg(long, value_name = "FILE", requires = "writable")]
148    pub mutations_file: Option<PathBuf>,
149
150    /// Force flush memtable to SSTable after writes. Requires --writable mode.
151    #[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    /// Start interactive REPL mode (basic text interface)
161    #[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    /// Start Terminal UI mode (full-screen interface)
166    #[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    /// Execute CQL queries against local SSTable data
171    #[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        /// CQL query to execute
176        query: String,
177        /// Show execution plan
178        #[arg(long)]
179        explain: bool,
180        /// Show query timing
181        #[arg(long)]
182        timing: bool,
183    },
184    /// Import data from file
185    Import {
186        /// Input file path
187        file: PathBuf,
188        /// Input format
189        #[arg(short, long, value_enum, default_value = "csv")]
190        format: ImportFormat,
191        /// Target table name
192        #[arg(short, long)]
193        table: String,
194        /// Column mapping (col1:field1,col2:field2)
195        #[arg(short, long)]
196        mapping: Option<String>,
197        /// Batch size for imports
198        #[arg(long, default_value = "1000")]
199        batch_size: usize,
200    },
201    /// Export data to file
202    Export {
203        /// Output file path
204        file: PathBuf,
205        /// Export format
206        #[arg(short, long, value_enum, default_value = "csv")]
207        format: ExportFormat,
208        /// Source table name
209        #[arg(short, long)]
210        table: String,
211        /// Query filter (WHERE clause)
212        #[arg(short, long)]
213        query: Option<String>,
214        /// Maximum number of rows to export
215        #[arg(short, long)]
216        limit: Option<usize>,
217    },
218    /// Administrative commands
219    Admin {
220        #[command(subcommand)]
221        command: AdminCommands,
222    },
223    /// Schema management
224    Schema {
225        #[command(subcommand)]
226        command: SchemaCommands,
227    },
228    /// Performance monitoring and benchmarks
229    Bench {
230        #[command(subcommand)]
231        command: BenchCommands,
232    },
233    /// Low-level SSTable inspection and reading
234    #[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        /// SSTable file or directory path
240        file: PathBuf,
241        /// Output format
242        #[arg(short, long, value_enum, default_value = "table")]
243        format: OutputFormat,
244        /// Limit number of rows displayed
245        #[arg(short, long)]
246        limit: Option<usize>,
247        /// Skip number of rows
248        #[arg(short, long, default_value = "0")]
249        skip: usize,
250        /// Show only keys
251        #[arg(long)]
252        keys_only: bool,
253        /// Show raw binary data
254        #[arg(long)]
255        raw: bool,
256        /// Enable detailed output
257        #[arg(long)]
258        verbose: bool,
259    },
260    /// Display file metadata and statistics
261    #[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        /// Target file or database path
266        path: Option<PathBuf>,
267        /// Output format
268        #[arg(short, long, value_enum, default_value = "text")]
269        format: InfoOutputFormat,
270        /// Show detailed information
271        #[arg(short, long)]
272        detailed: bool,
273    },
274    /// Run background maintenance (compaction) - requires --writable mode
275    #[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    /// Display write engine statistics - requires --writable mode
280    #[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    /// Export SSTables for Cassandra import - requires --writable mode
286    #[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    /// Display database information
296    Info,
297    /// Compact database files
298    Compact {
299        /// Force compaction even if not needed
300        #[arg(long)]
301        force: bool,
302    },
303    /// Backup database
304    Backup {
305        /// Backup destination path
306        destination: PathBuf,
307        /// Compression level (0-9)
308        #[arg(long, default_value = "6")]
309        compression: u8,
310    },
311    /// Restore database from backup
312    Restore {
313        /// Backup file path
314        backup: PathBuf,
315        /// Force restore (overwrite existing)
316        #[arg(long)]
317        force: bool,
318    },
319}
320
321#[derive(Subcommand)]
322pub enum SchemaCommands {
323    /// List all tables
324    List,
325    /// Describe table structure
326    Describe {
327        /// Table name
328        table: String,
329    },
330    /// Create table from schema file
331    Create {
332        /// Schema file path
333        schema: PathBuf,
334    },
335    /// Drop table
336    Drop {
337        /// Table name
338        table: String,
339        /// Force drop (ignore dependencies)
340        #[arg(long)]
341        force: bool,
342    },
343    /// Load schemas from files or directories
344    Load {
345        /// Schema file (.cql or .json) or directory paths (repeatable, processed in order)
346        #[arg(required = true)]
347        paths: Vec<PathBuf>,
348    },
349}
350
351#[derive(Subcommand)]
352pub enum BenchCommands {
353    /// Run read performance benchmark
354    Read {
355        /// Number of operations
356        #[arg(short, long, default_value = "10000")]
357        operations: usize,
358        /// Concurrency level
359        #[arg(short, long, default_value = "1")]
360        concurrency: usize,
361        /// Target table
362        #[arg(short, long)]
363        table: Option<String>,
364    },
365    /// Run write performance benchmark
366    Write {
367        /// Number of operations
368        #[arg(short, long, default_value = "10000")]
369        operations: usize,
370        /// Concurrency level
371        #[arg(short, long, default_value = "1")]
372        concurrency: usize,
373        /// Target table
374        #[arg(short, long)]
375        table: Option<String>,
376    },
377    /// Run mixed workload benchmark
378    Mixed {
379        /// Number of operations
380        #[arg(short, long, default_value = "10000")]
381        operations: usize,
382        /// Read/write ratio (0.0-1.0)
383        #[arg(long, default_value = "0.8")]
384        read_ratio: f64,
385        /// Concurrency level
386        #[arg(short, long, default_value = "1")]
387        concurrency: usize,
388    },
389}
390
391// Arguments for the maintenance subcommand (Issue #392)
392#[derive(Args, Debug, Clone)]
393pub struct MaintenanceArgs {
394    /// Time budget in milliseconds for this maintenance step
395    #[arg(long, default_value = "100")]
396    pub budget_ms: u64,
397}
398
399// Arguments for the export-sstable subcommand (Issue #392)
400#[derive(Args, Debug, Clone)]
401pub struct ExportSstableArgs {
402    /// Output directory for exported SSTables
403    pub output: PathBuf,
404    /// Keyspace name for the exported SSTable
405    #[arg(long, default_value = "export")]
406    pub keyspace: String,
407    /// Table name for the exported SSTable
408    #[arg(long, default_value = "data")]
409    pub table: String,
410    /// Run compaction before export to merge multiple SSTables
411    #[arg(long)]
412    pub compact: bool,
413    /// Skip validation after export
414    #[arg(long)]
415    pub skip_validate: bool,
416}