1use clap::Parser;
7use std::path::PathBuf;
8
9#[derive(Parser, Debug)]
14#[command(
15 name = "sql-stream",
16 version,
17 author,
18 about = "Execute SQL queries against CSV/JSON files with streaming",
19 long_about = "A production-grade CLI tool that executes SQL queries against CSV/JSON files \
20 using Apache DataFusion and Apache Arrow with zero-copy, streaming architecture."
21)]
22pub struct CliArgs {
23 #[arg(
25 short = 'f',
26 long = "file",
27 value_name = "FILE",
28 help = "Path to CSV or JSON file",
29 required = true
30 )]
31 pub file: PathBuf,
32
33 #[arg(
35 short = 'q',
36 long = "query",
37 value_name = "SQL",
38 help = "SQL query string to execute",
39 required = true
40 )]
41 pub query: String,
42
43 #[arg(
45 short = 't',
46 long = "table-name",
47 value_name = "NAME",
48 help = "Table name to use in SQL queries",
49 default_value = "data"
50 )]
51 pub table_name: String,
52
53 #[arg(short = 'v', long = "verbose", help = "Enable verbose logging output")]
55 pub verbose: bool,
56}
57
58impl CliArgs {
59 pub fn parse() -> Self {
68 <Self as Parser>::parse()
69 }
70
71 pub fn validate(&self) -> Result<(), String> {
79 if !self.file.exists() {
81 return Err(format!("File not found: {}", self.file.display()));
82 }
83
84 let extension = self
86 .file
87 .extension()
88 .and_then(|ext| ext.to_str())
89 .ok_or_else(|| "File must have an extension (.csv or .json)".to_string())?;
90
91 match extension.to_lowercase().as_str() {
92 "csv" | "json" => Ok(()),
93 _ => Err(format!(
94 "Unsupported file extension: .{}. Supported: .csv, .json",
95 extension
96 )),
97 }
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_cli_structure() {
107 let args = CliArgs {
110 file: PathBuf::from("test.csv"),
111 query: "SELECT * FROM data".to_string(),
112 table_name: "data".to_string(),
113 verbose: false,
114 };
115
116 assert_eq!(args.table_name, "data");
117 assert_eq!(args.query, "SELECT * FROM data");
118 }
119}