1use 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
13pub 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 if !file_path.exists() {
27 return Err(anyhow::anyhow!(
28 "SSTable file not found: {}",
29 file_path.display()
30 ));
31 }
32
33 let pb = create_progress_bar("Opening SSTable");
35
36 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 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 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 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 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 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
131fn 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
143fn 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 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 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
191fn 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
224fn 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 if keys_only {
234 wtr.write_record(["table_id", "key"])?;
235 } else {
236 wtr.write_record(["table_id", "key", "value"])?;
237 }
238
239 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
256fn format_row_key(key: &RowKey, _raw: bool) -> String {
258 format!("{:?}", key)
261}
262
263fn format_value(value: &Value, raw: bool) -> String {
265 if raw {
266 format!("{:?}", value)
268 } else {
269 format!("{}", value)
271 }
272}