Skip to main content

cqlite_cli/
cli.rs

1use anyhow::{anyhow, Context, Result};
2use clap::ValueEnum;
3use std::path::PathBuf;
4
5#[derive(ValueEnum, Clone, Copy, Debug, PartialEq)]
6pub enum OutputFormat {
7    Table,
8    Json,
9    Csv,
10    /// Parquet binary format (requires --output flag for file destination)
11    Parquet,
12}
13
14#[derive(ValueEnum, Clone, Debug)]
15pub enum ImportFormat {
16    Csv,
17    Json,
18    Parquet,
19}
20
21#[derive(ValueEnum, Clone, Debug)]
22pub enum ExportFormat {
23    Csv,
24    Json,
25    Parquet,
26    Cql,
27}
28
29#[derive(ValueEnum, Clone, Debug)]
30pub enum InfoOutputFormat {
31    Text,
32    Json,
33    Csv,
34}
35
36impl std::fmt::Display for OutputFormat {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        match self {
39            OutputFormat::Table => write!(f, "table"),
40            OutputFormat::Json => write!(f, "json"),
41            OutputFormat::Csv => write!(f, "csv"),
42            OutputFormat::Parquet => write!(f, "parquet"),
43        }
44    }
45}
46
47impl std::fmt::Display for ImportFormat {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        match self {
50            ImportFormat::Csv => write!(f, "csv"),
51            ImportFormat::Json => write!(f, "json"),
52            ImportFormat::Parquet => write!(f, "parquet"),
53        }
54    }
55}
56
57impl std::fmt::Display for ExportFormat {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        match self {
60            ExportFormat::Csv => write!(f, "csv"),
61            ExportFormat::Json => write!(f, "json"),
62            ExportFormat::Parquet => write!(f, "parquet"),
63            ExportFormat::Cql => write!(f, "cql"),
64        }
65    }
66}
67
68impl std::fmt::Display for InfoOutputFormat {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        match self {
71            InfoOutputFormat::Text => write!(f, "text"),
72            InfoOutputFormat::Json => write!(f, "json"),
73            InfoOutputFormat::Csv => write!(f, "csv"),
74        }
75    }
76}
77
78impl std::str::FromStr for OutputFormat {
79    type Err = String;
80
81    fn from_str(s: &str) -> Result<Self, Self::Err> {
82        match s.to_lowercase().as_str() {
83            "table" => Ok(OutputFormat::Table),
84            "json" => Ok(OutputFormat::Json),
85            "csv" => Ok(OutputFormat::Csv),
86            _ => Err(format!("Invalid output format: {}", s)),
87        }
88    }
89}
90
91/// Supported Cassandra versions for compatibility
92#[derive(Debug, Clone, PartialEq)]
93#[allow(dead_code)]
94pub enum CassandraVersion {
95    V311,
96    V400,
97    V500,
98    Unknown(String),
99}
100
101#[allow(dead_code)]
102impl CassandraVersion {
103    pub fn from_string(version: &str) -> Self {
104        match version {
105            "3.11" | "3.11.0" => CassandraVersion::V311,
106            "4.0" | "4.0.0" => CassandraVersion::V400,
107            "5.0" | "5.0.0" => CassandraVersion::V500,
108            _ => CassandraVersion::Unknown(version.to_string()),
109        }
110    }
111
112    pub fn to_string(&self) -> String {
113        match self {
114            CassandraVersion::V311 => "3.11".to_string(),
115            CassandraVersion::V400 => "4.0".to_string(),
116            CassandraVersion::V500 => "5.0".to_string(),
117            CassandraVersion::Unknown(v) => v.clone(),
118        }
119    }
120
121    pub fn is_supported(&self) -> bool {
122        matches!(
123            self,
124            CassandraVersion::V311 | CassandraVersion::V400 | CassandraVersion::V500
125        )
126    }
127}
128
129/// SSTable format version detection
130#[allow(dead_code)]
131pub fn detect_sstable_version(sstable_path: &PathBuf) -> Result<String> {
132    use std::fs::File;
133    use std::io::{Read, Seek, SeekFrom};
134
135    let mut file = File::open(sstable_path)
136        .with_context(|| format!("Failed to open SSTable file: {}", sstable_path.display()))?;
137
138    // Read first few bytes to detect format
139    let mut header = vec![0u8; 16];
140    file.read_exact(&mut header)
141        .with_context(|| "Failed to read SSTable header for version detection")?;
142
143    // Basic version detection based on file header patterns
144    // This is a simplified implementation - real detection would be more sophisticated
145    if header.starts_with(b"SSTA") {
146        // Check for specific version markers
147        file.seek(SeekFrom::Start(4))?;
148        let mut version_bytes = vec![0u8; 4];
149        file.read_exact(&mut version_bytes)?;
150
151        let version = u32::from_be_bytes([
152            version_bytes[0],
153            version_bytes[1],
154            version_bytes[2],
155            version_bytes[3],
156        ]);
157
158        match version {
159            0x0001 => Ok("3.11".to_string()),
160            0x0002 => Ok("4.0".to_string()),
161            0x0003 => Ok("5.0".to_string()),
162            _ => Ok("unknown".to_string()),
163        }
164    } else {
165        // Try to detect based on file structure patterns
166        file.seek(SeekFrom::End(-512))?;
167        let mut footer = vec![0u8; 512];
168        file.read(&mut footer)?;
169
170        if footer.iter().any(|&b| b != 0) {
171            // Has footer - likely newer format
172            Ok("4.0".to_string())
173        } else {
174            // No footer - likely older format
175            Ok("3.11".to_string())
176        }
177    }
178}
179
180/// Validate Cassandra version string
181#[allow(dead_code)]
182pub fn validate_cassandra_version(version: &str) -> Result<CassandraVersion> {
183    let cassandra_version = CassandraVersion::from_string(version);
184
185    if !cassandra_version.is_supported() {
186        return Err(anyhow!(
187            "Unsupported Cassandra version: {}. Supported versions: 3.11, 4.0, 5.0",
188            version
189        ));
190    }
191
192    Ok(cassandra_version)
193}
194
195/// Create enhanced error message with version context
196#[allow(dead_code)]
197pub fn create_version_error(
198    base_error: &str,
199    detected_version: Option<&str>,
200    provided_version: Option<&str>,
201) -> String {
202    let mut error_msg = base_error.to_string();
203
204    if let Some(detected) = detected_version {
205        error_msg.push_str(&format!("\nDetected SSTable version: {detected}"));
206    }
207
208    if let Some(provided) = provided_version {
209        error_msg.push_str(&format!("\nProvided Cassandra version: {provided}"));
210    }
211
212    error_msg.push_str("\nHint: Try using --auto-detect flag for automatic version detection");
213    error_msg
214        .push_str("\n      or specify --cassandra-version to override (supported: 3.11, 4.0, 5.0)");
215
216    error_msg
217}