Skip to main content

cqlite_cli/commands/
read_sstable.rs

1// Read SSTable Command Implementation - Issue #124
2// Direct SSTable reading and display with intelligent formatting
3
4use anyhow::{Context, Result};
5use cqlite_core::types::TableId;
6use cqlite_core::{storage::sstable::reader::SSTableReader, Config as CoreConfig, RowKey, Value};
7use indicatif::{ProgressBar, ProgressStyle};
8use std::path::Path;
9use std::sync::Arc;
10
11use crate::cli::OutputFormat;
12
13/// Execute the read-sstable command
14pub async fn execute_read_sstable_command(
15    file_path: &Path,
16    format: OutputFormat,
17    limit: Option<usize>,
18    skip: usize,
19    keys_only: bool,
20    raw: bool,
21    verbose: bool,
22) -> Result<()> {
23    eprintln!("šŸ“– Reading SSTable: {}", file_path.display());
24
25    // Validate file exists
26    if !file_path.exists() {
27        return Err(anyhow::anyhow!(
28            "SSTable file not found: {}",
29            file_path.display()
30        ));
31    }
32
33    // Create progress indicator
34    let pb = create_progress_bar("Opening SSTable");
35
36    // Initialize Platform and Config (following initialize_database pattern)
37    let config = CoreConfig::default();
38    let platform = Arc::new(
39        cqlite_core::platform::Platform::new(&config)
40            .await
41            .with_context(|| {
42                format!(
43                    "Failed to initialize platform for SSTable at {}. \
44                     Check file permissions and system resources.",
45                    file_path.display()
46                )
47            })?,
48    );
49
50    pb.set_message("Opening SSTable reader...");
51
52    // Open the SSTable
53    let reader = SSTableReader::open(file_path, &config, platform)
54        .await
55        .with_context(|| format!("Failed to open SSTable: {}", file_path.display()))?;
56
57    pb.set_message("Reading SSTable entries...");
58
59    // Read all entries from the SSTable
60    let entries = reader
61        .get_all_entries()
62        .await
63        .context("Failed to read SSTable entries")?;
64
65    let total_entries = entries.len();
66    pb.finish_with_message(format!("āœ… Read {} entries", total_entries));
67
68    // Show basic stats if verbose
69    if verbose {
70        let stats = reader.stats().await?;
71        eprintln!("\nšŸ“Š SSTable Statistics:");
72        eprintln!("  Total entries: {}", stats.entry_count);
73        eprintln!("  Table count: {}", stats.table_count);
74        eprintln!("  Block count: {}", stats.block_count);
75        eprintln!("  Index size: {} bytes", stats.index_size);
76        eprintln!("  Bloom filter size: {} bytes", stats.bloom_filter_size);
77        eprintln!(
78            "  Compression ratio: {:.2}%",
79            stats.compression_ratio * 100.0
80        );
81        eprintln!("  Cache hit rate: {:.2}%", stats.cache_hit_rate * 100.0);
82        eprintln!();
83    }
84
85    // Apply skip and limit pagination
86    let display_entries: Vec<_> = entries
87        .into_iter()
88        .skip(skip)
89        .take(limit.unwrap_or(usize::MAX))
90        .collect();
91
92    let displayed_count = display_entries.len();
93
94    if displayed_count == 0 {
95        eprintln!(
96            "No entries to display (total: {}, skip: {})",
97            total_entries, skip
98        );
99        return Ok(());
100    }
101
102    eprintln!(
103        "Displaying {} of {} entries (skip: {})\n",
104        displayed_count, total_entries, skip
105    );
106
107    // Display based on format
108    match format {
109        OutputFormat::Table => {
110            display_table_format(&display_entries, keys_only, raw)?;
111        }
112        OutputFormat::Json => {
113            display_json_format(&display_entries, keys_only, raw)?;
114        }
115        OutputFormat::Csv => {
116            display_csv_format(&display_entries, keys_only, raw)?;
117        }
118        OutputFormat::Parquet => {
119            return Err(anyhow::anyhow!("Parquet format is not supported for this command. Use --out json or --out csv instead."));
120        }
121    }
122
123    eprintln!(
124        "\nāœ… Displayed {} entries (total: {}, skipped: {})",
125        displayed_count, total_entries, skip
126    );
127
128    Ok(())
129}
130
131/// Create a progress bar for operations
132fn create_progress_bar(message: &str) -> ProgressBar {
133    let pb = ProgressBar::new_spinner();
134    pb.set_style(
135        ProgressStyle::default_spinner()
136            .template("{spinner:.green} [{elapsed_precise}] {msg}")
137            .expect("Failed to create progress bar template"),
138    );
139    pb.set_message(message.to_string());
140    pb
141}
142
143/// Display entries in table format
144fn display_table_format(
145    entries: &[(TableId, RowKey, Value)],
146    keys_only: bool,
147    raw: bool,
148) -> Result<()> {
149    use prettytable::{Cell, Row, Table};
150
151    let mut table = Table::new();
152
153    // Add header
154    if keys_only {
155        table.set_titles(Row::new(vec![
156            Cell::new("#"),
157            Cell::new("Table ID"),
158            Cell::new("Row Key"),
159        ]));
160    } else {
161        table.set_titles(Row::new(vec![
162            Cell::new("#"),
163            Cell::new("Table ID"),
164            Cell::new("Row Key"),
165            Cell::new("Value"),
166        ]));
167    }
168
169    // Add data rows
170    for (idx, (table_id, key, value)) in entries.iter().enumerate() {
171        let key_str = format_row_key(key, raw);
172        let table_id_str = table_id.to_string();
173        let mut row = Row::new(vec![
174            Cell::new(&(idx + 1).to_string()),
175            Cell::new(&table_id_str),
176            Cell::new(&key_str),
177        ]);
178
179        if !keys_only {
180            let value_str = format_value(value, raw);
181            row.add_cell(Cell::new(&value_str));
182        }
183
184        table.add_row(row);
185    }
186
187    table.printstd();
188    Ok(())
189}
190
191/// Display entries in JSON format
192fn display_json_format(
193    entries: &[(TableId, RowKey, Value)],
194    keys_only: bool,
195    raw: bool,
196) -> Result<()> {
197    let mut json_entries = Vec::new();
198
199    for (table_id, key, value) in entries {
200        let key_str = format_row_key(key, raw);
201        let table_id_str = table_id.to_string();
202
203        let entry = if keys_only {
204            serde_json::json!({
205                "table_id": table_id_str,
206                "key": key_str,
207            })
208        } else {
209            let value_str = format_value(value, raw);
210            serde_json::json!({
211                "table_id": table_id_str,
212                "key": key_str,
213                "value": value_str,
214            })
215        };
216
217        json_entries.push(entry);
218    }
219
220    println!("{}", serde_json::to_string_pretty(&json_entries)?);
221    Ok(())
222}
223
224/// Display entries in CSV format
225fn display_csv_format(
226    entries: &[(TableId, RowKey, Value)],
227    keys_only: bool,
228    raw: bool,
229) -> Result<()> {
230    let mut wtr = csv::Writer::from_writer(std::io::stdout());
231
232    // Write header
233    if keys_only {
234        wtr.write_record(["table_id", "key"])?;
235    } else {
236        wtr.write_record(["table_id", "key", "value"])?;
237    }
238
239    // Write data rows
240    for (table_id, key, value) in entries {
241        let key_str = format_row_key(key, raw);
242        let table_id_str = table_id.to_string();
243
244        if keys_only {
245            wtr.write_record([&table_id_str, &key_str])?;
246        } else {
247            let value_str = format_value(value, raw);
248            wtr.write_record([&table_id_str, &key_str, &value_str])?;
249        }
250    }
251
252    wtr.flush()?;
253    Ok(())
254}
255
256/// Format a row key for display
257fn format_row_key(key: &RowKey, _raw: bool) -> String {
258    // Note: Raw formatting not yet implemented for RowKey
259    // Both modes use Debug format currently
260    format!("{:?}", key)
261}
262
263/// Format a value for display
264fn format_value(value: &Value, raw: bool) -> String {
265    if raw {
266        // Show raw representation
267        format!("{:?}", value)
268    } else {
269        // Use Display trait for user-friendly output
270        format!("{}", value)
271    }
272}