use anyhow::{Context, Result};
use cqlite_core::types::TableId;
use cqlite_core::{storage::sstable::reader::SSTableReader, Config as CoreConfig, RowKey, Value};
use indicatif::{ProgressBar, ProgressStyle};
use std::path::Path;
use std::sync::Arc;
use crate::cli::OutputFormat;
pub async fn execute_read_sstable_command(
file_path: &Path,
format: OutputFormat,
limit: Option<usize>,
skip: usize,
keys_only: bool,
raw: bool,
verbose: bool,
) -> Result<()> {
eprintln!("📖 Reading SSTable: {}", file_path.display());
if !file_path.exists() {
return Err(anyhow::anyhow!(
"SSTable file not found: {}",
file_path.display()
));
}
let pb = create_progress_bar("Opening SSTable");
let config = CoreConfig::default();
let platform = Arc::new(
cqlite_core::platform::Platform::new(&config)
.await
.with_context(|| {
format!(
"Failed to initialize platform for SSTable at {}. \
Check file permissions and system resources.",
file_path.display()
)
})?,
);
pb.set_message("Opening SSTable reader...");
let reader = SSTableReader::open(file_path, &config, platform)
.await
.with_context(|| format!("Failed to open SSTable: {}", file_path.display()))?;
pb.set_message("Reading SSTable entries...");
let entries = reader
.get_all_entries()
.await
.context("Failed to read SSTable entries")?;
let total_entries = entries.len();
pb.finish_with_message(format!("✅ Read {} entries", total_entries));
if verbose {
let stats = reader.stats().await?;
eprintln!("\n📊 SSTable Statistics:");
eprintln!(" Total entries: {}", stats.entry_count);
eprintln!(" Table count: {}", stats.table_count);
eprintln!(" Block count: {}", stats.block_count);
eprintln!(" Index size: {} bytes", stats.index_size);
eprintln!(" Bloom filter size: {} bytes", stats.bloom_filter_size);
eprintln!(
" Compression ratio: {:.2}%",
stats.compression_ratio * 100.0
);
eprintln!(" Cache hit rate: {:.2}%", stats.cache_hit_rate * 100.0);
eprintln!();
}
let display_entries: Vec<_> = entries
.into_iter()
.skip(skip)
.take(limit.unwrap_or(usize::MAX))
.collect();
let displayed_count = display_entries.len();
if displayed_count == 0 {
eprintln!(
"No entries to display (total: {}, skip: {})",
total_entries, skip
);
return Ok(());
}
eprintln!(
"Displaying {} of {} entries (skip: {})\n",
displayed_count, total_entries, skip
);
match format {
OutputFormat::Table => {
display_table_format(&display_entries, keys_only, raw)?;
}
OutputFormat::Json => {
display_json_format(&display_entries, keys_only, raw)?;
}
OutputFormat::Csv => {
display_csv_format(&display_entries, keys_only, raw)?;
}
OutputFormat::Parquet => {
return Err(anyhow::anyhow!("Parquet format is not supported for this command. Use --out json or --out csv instead."));
}
}
eprintln!(
"\n✅ Displayed {} entries (total: {}, skipped: {})",
displayed_count, total_entries, skip
);
Ok(())
}
fn create_progress_bar(message: &str) -> ProgressBar {
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::default_spinner()
.template("{spinner:.green} [{elapsed_precise}] {msg}")
.expect("Failed to create progress bar template"),
);
pb.set_message(message.to_string());
pb
}
fn display_table_format(
entries: &[(TableId, RowKey, Value)],
keys_only: bool,
raw: bool,
) -> Result<()> {
use prettytable::{Cell, Row, Table};
let mut table = Table::new();
if keys_only {
table.set_titles(Row::new(vec![
Cell::new("#"),
Cell::new("Table ID"),
Cell::new("Row Key"),
]));
} else {
table.set_titles(Row::new(vec![
Cell::new("#"),
Cell::new("Table ID"),
Cell::new("Row Key"),
Cell::new("Value"),
]));
}
for (idx, (table_id, key, value)) in entries.iter().enumerate() {
let key_str = format_row_key(key, raw);
let table_id_str = table_id.to_string();
let mut row = Row::new(vec![
Cell::new(&(idx + 1).to_string()),
Cell::new(&table_id_str),
Cell::new(&key_str),
]);
if !keys_only {
let value_str = format_value(value, raw);
row.add_cell(Cell::new(&value_str));
}
table.add_row(row);
}
table.printstd();
Ok(())
}
fn display_json_format(
entries: &[(TableId, RowKey, Value)],
keys_only: bool,
raw: bool,
) -> Result<()> {
let mut json_entries = Vec::new();
for (table_id, key, value) in entries {
let key_str = format_row_key(key, raw);
let table_id_str = table_id.to_string();
let entry = if keys_only {
serde_json::json!({
"table_id": table_id_str,
"key": key_str,
})
} else {
let value_str = format_value(value, raw);
serde_json::json!({
"table_id": table_id_str,
"key": key_str,
"value": value_str,
})
};
json_entries.push(entry);
}
println!("{}", serde_json::to_string_pretty(&json_entries)?);
Ok(())
}
fn display_csv_format(
entries: &[(TableId, RowKey, Value)],
keys_only: bool,
raw: bool,
) -> Result<()> {
let mut wtr = csv::Writer::from_writer(std::io::stdout());
if keys_only {
wtr.write_record(["table_id", "key"])?;
} else {
wtr.write_record(["table_id", "key", "value"])?;
}
for (table_id, key, value) in entries {
let key_str = format_row_key(key, raw);
let table_id_str = table_id.to_string();
if keys_only {
wtr.write_record([&table_id_str, &key_str])?;
} else {
let value_str = format_value(value, raw);
wtr.write_record([&table_id_str, &key_str, &value_str])?;
}
}
wtr.flush()?;
Ok(())
}
fn format_row_key(key: &RowKey, _raw: bool) -> String {
format!("{:?}", key)
}
fn format_value(value: &Value, raw: bool) -> String {
if raw {
format!("{:?}", value)
} else {
format!("{}", value)
}
}