1#![allow(deprecated)]
7
8use anyhow::{Context, Result};
9use cqlite_core::{
10 schema::{parse_cql_schema, TableSchema},
11 storage::sstable::{
12 bulletproof_reader::BulletproofReader, directory::SSTableDirectory, reader::SSTableReader,
13 statistics_reader::find_statistics_file,
14 },
15 Config,
16};
17use csv;
18use indicatif::{ProgressBar, ProgressStyle};
19use serde_json;
20use std::collections::HashMap;
21use std::path::{Path, PathBuf};
22use std::sync::Arc;
23use tracing::{info, warn};
24
25use crate::cli::{
26 create_version_error, detect_sstable_version, validate_cassandra_version, InfoOutputFormat,
27};
28
29pub async fn execute_info_command(
31 sstable_path: &Path,
32 detailed: bool,
33 format: InfoOutputFormat,
34 validate: bool,
35 schema_path: Option<&Path>,
36 auto_detect: bool,
37 cassandra_version: Option<String>,
38) -> Result<()> {
39 println!("š CQLite SSTable Info Analysis");
40 println!("================================");
41
42 let pb = create_progress_bar("Analyzing SSTable");
44 pb.set_message("Detecting format...");
45
46 let detected_version = if auto_detect {
48 match detect_sstable_version(&sstable_path.to_path_buf()) {
49 Ok(version) => {
50 info!("Auto-detected SSTable version: {}", version);
51 pb.set_message("Version detected");
52 Some(version)
53 }
54 Err(e) => {
55 warn!("Failed to auto-detect version: {}", e);
56 pb.set_message("Version detection failed");
57 None
58 }
59 }
60 } else {
61 None
62 };
63
64 let validated_version = if let Some(ref version) = cassandra_version {
66 match validate_cassandra_version(version) {
67 Ok(v) => {
68 info!("Using Cassandra version: {:?}", v);
69 pb.set_message("Cassandra version validated");
70 Some(v.to_string())
71 }
72 Err(e) => {
73 pb.finish_with_message("ā Version validation failed");
74 return Err(anyhow::anyhow!(create_version_error(
75 &format!("Invalid Cassandra version: {e}"),
76 detected_version.as_deref(),
77 Some(version)
78 )));
79 }
80 }
81 } else {
82 None
83 };
84
85 pb.set_message("Analyzing SSTable structure...");
86
87 let info_result = if sstable_path.is_dir() {
89 analyze_sstable_directory(
90 sstable_path,
91 detected_version.clone(),
92 validated_version.clone(),
93 detailed,
94 validate,
95 schema_path,
96 &pb,
97 )
98 .await
99 } else {
100 analyze_sstable_file(
101 sstable_path,
102 detected_version.clone(),
103 validated_version.clone(),
104 detailed,
105 validate,
106 schema_path,
107 &pb,
108 )
109 .await
110 };
111
112 match info_result {
113 Ok(metadata) => {
114 pb.finish_with_message("ā
Analysis complete");
115 display_info_output(metadata, format).await
116 }
117 Err(e) => {
118 pb.finish_with_message("ā Analysis failed");
119 Err(e)
120 }
121 }
122}
123
124async fn analyze_sstable_directory(
126 directory_path: &Path,
127 detected_version: Option<String>,
128 validated_version: Option<String>,
129 _detailed: bool,
130 validate: bool,
131 schema_path: Option<&Path>,
132 pb: &ProgressBar,
133) -> Result<SSTableInfoMetadata> {
134 pb.set_message("Validating directory structure...");
135
136 SSTableDirectory::validate_directory_path(directory_path)
138 .with_context(|| format!("Invalid SSTable directory: {}", directory_path.display()))?;
139
140 pb.set_message("Scanning directory contents...");
141
142 let directory = SSTableDirectory::scan(directory_path).with_context(|| {
144 format!(
145 "Failed to scan SSTable directory: {}",
146 directory_path.display()
147 )
148 })?;
149
150 pb.set_message("Analyzing generations...");
151
152 let mut total_size = 0u64;
154 let mut total_components = 0;
155 let mut generation_info = Vec::new();
156
157 for generation in &directory.generations {
158 let mut gen_size = 0u64;
159 let mut component_details = Vec::new();
160
161 for (component, path) in &generation.components {
162 if let Ok(metadata) = std::fs::metadata(path) {
163 gen_size += metadata.len();
164 component_details.push(ComponentInfo {
165 component_type: format!("{component:?}"),
166 file_path: path.clone(),
167 file_size: metadata.len(),
168 });
169 }
170 total_components += 1;
171 }
172
173 total_size += gen_size;
174 generation_info.push(GenerationInfo {
175 generation: generation.generation,
176 format: generation.format.clone(),
177 size: gen_size,
178 components: component_details,
179 });
180 }
181
182 let schema_info = if let Some(schema_path) = schema_path {
184 pb.set_message("Loading schema information...");
185 load_schema_info(schema_path).await.ok()
186 } else {
187 None
188 };
189
190 let validation_result = if validate {
192 pb.set_message("Validating directory integrity...");
193 Some(perform_directory_validation(&directory).await)
194 } else {
195 None
196 };
197
198 let bulletproof_info = if let Some(data_path) = find_data_file_in_directory(directory_path) {
200 pb.set_message("Analyzing with bulletproof reader...");
201 analyze_with_bulletproof_reader(&data_path).await.ok()
202 } else {
203 None
204 };
205
206 Ok(SSTableInfoMetadata {
207 file_path: directory_path.to_path_buf(),
208 file_type: SSTableFileType::Directory,
209 file_size: total_size,
210 detected_version: detected_version.clone(),
211 validated_version,
212 format_features: get_format_features(detected_version.as_deref()),
213 schema_info,
214 validation_result,
215 directory_info: Some(DirectoryInfo {
216 table_name: directory.table_name.clone(),
217 generations: directory.generations.len(),
218 is_valid: directory.is_valid(),
219 total_components,
220 generation_info,
221 }),
222 bulletproof_info,
223 statistics_file: None, entry_count: 0,
226 table_count: 0,
227 block_count: 0,
228 index_size: 0,
229 bloom_filter_size: 0,
230 compression_ratio: 0.0,
231 cache_hit_rate: 0.0,
232 })
233}
234
235async fn analyze_sstable_file(
237 file_path: &Path,
238 detected_version: Option<String>,
239 validated_version: Option<String>,
240 _detailed: bool,
241 validate: bool,
242 schema_path: Option<&Path>,
243 pb: &ProgressBar,
244) -> Result<SSTableInfoMetadata> {
245 pb.set_message("Opening SSTable file...");
246
247 let file_size = std::fs::metadata(file_path)
249 .with_context(|| format!("Failed to get file metadata: {}", file_path.display()))?
250 .len();
251
252 let bulletproof_info = {
254 pb.set_message("Analyzing with bulletproof reader...");
255 analyze_with_bulletproof_reader(file_path).await.ok()
256 };
257
258 pb.set_message("Loading SSTable reader...");
260 let config = Config::default();
261 let platform = Arc::new(cqlite_core::platform::Platform::new(&config).await?);
262 let reader = SSTableReader::open(file_path, &config, platform.clone())
263 .await
264 .with_context(|| format!("Failed to open SSTable: {}", file_path.display()))?;
265
266 pb.set_message("Reading SSTable statistics...");
267 let reader_stats = reader.stats().await?;
268
269 let schema_info = if let Some(schema_path) = schema_path {
271 pb.set_message("Loading schema information...");
272 load_schema_info(schema_path).await.ok()
273 } else {
274 None
275 };
276
277 let statistics_file = {
279 pb.set_message("Looking for Statistics.db...");
280 find_statistics_file(file_path)
281 .await
282 .map(|path| (path.to_path_buf(), "Statistics.db file found".to_string()))
283 };
284
285 let validation_result = if validate {
287 pb.set_message("Validating file integrity...");
288 perform_file_validation(file_path, &reader).await.ok()
289 } else {
290 None
291 };
292
293 Ok(SSTableInfoMetadata {
294 file_path: file_path.to_path_buf(),
295 file_type: SSTableFileType::SingleFile,
296 file_size,
297 detected_version: detected_version.clone(),
298 validated_version,
299 format_features: get_format_features(detected_version.as_deref()),
300 schema_info,
301 validation_result,
302 directory_info: None,
303 bulletproof_info,
304 statistics_file,
305 entry_count: reader_stats.entry_count,
307 table_count: reader_stats.table_count as u32,
308 block_count: reader_stats.block_count as u32,
309 index_size: reader_stats.index_size,
310 bloom_filter_size: reader_stats.bloom_filter_size,
311 compression_ratio: reader_stats.compression_ratio,
312 cache_hit_rate: reader_stats.cache_hit_rate,
313 })
314}
315
316async fn analyze_with_bulletproof_reader(file_path: &Path) -> Result<BulletproofInfo> {
318 let mut reader = BulletproofReader::open(file_path).with_context(|| {
319 format!(
320 "Failed to open with bulletproof reader: {}",
321 file_path.display()
322 )
323 })?;
324
325 let info = reader.info();
327 let format_info = format!("{:?}", info.format);
328 let generation = info.generation_numeric().unwrap_or(0) as u32;
329 let detected_size = info.size.parse().unwrap_or(0);
330
331 let compression_info = reader.compression_info().map(|c| CompressionInfo {
332 algorithm: c.algorithm.clone(),
333 chunk_length: c.chunk_length,
334 });
335
336 let integrity_check = reader.verify_integrity().await.ok();
337
338 let data_sample = match reader.parse_sstable_data() {
340 Ok(entries) => Some(DataSampleInfo {
341 total_entries: entries.len(),
342 sample_entries: entries
343 .into_iter()
344 .take(5)
345 .map(|e| format!("{e:?}"))
346 .collect(),
347 }),
348 Err(_) => None,
349 };
350
351 Ok(BulletproofInfo {
352 format: format_info,
353 generation,
354 detected_size,
355 compression_info,
356 integrity_check,
357 data_sample,
358 })
359}
360
361pub async fn load_schema_info(schema_path: &Path) -> Result<SchemaInfo> {
363 let schema = load_schema_file(schema_path)?;
364
365 let keyspace = schema.keyspace.clone();
366 let table = schema.table.clone();
367 let partition_keys = schema
368 .partition_keys
369 .iter()
370 .map(|k| k.name.clone())
371 .collect();
372 let clustering_keys = schema
373 .clustering_keys
374 .iter()
375 .map(|k| k.name.clone())
376 .collect();
377 let columns = schema
378 .columns
379 .iter()
380 .map(|c| ColumnInfo {
381 name: c.name.clone(),
382 data_type: c.data_type.clone(),
383 kind: determine_column_kind(&schema, &c.name),
384 })
385 .collect();
386
387 Ok(SchemaInfo {
388 keyspace,
389 table,
390 columns,
391 partition_keys,
392 clustering_keys,
393 })
394}
395
396fn load_schema_file(schema_path: &Path) -> Result<TableSchema> {
398 let schema_content = std::fs::read_to_string(schema_path)
399 .with_context(|| format!("Failed to read schema file: {}", schema_path.display()))?;
400
401 let extension = schema_path
402 .extension()
403 .and_then(|s| s.to_str())
404 .unwrap_or("")
405 .to_lowercase();
406
407 match extension.as_str() {
408 "json" => parse_json_schema(&schema_content),
409 "cql" | "sql" | "" => {
410 parse_cql_schema(&schema_content).with_context(|| "Failed to parse CQL schema")
411 }
412 _ => Err(anyhow::anyhow!(
413 "Unsupported schema file extension: .{}",
414 extension
415 )),
416 }
417}
418
419fn parse_json_schema(schema_content: &str) -> Result<TableSchema> {
421 let json: serde_json::Value = serde_json::from_str(schema_content)?;
423
424 let keyspace = json["keyspace"]
425 .as_str()
426 .ok_or_else(|| anyhow::anyhow!("Missing keyspace in schema"))?;
427 let table = json["table"]
428 .as_str()
429 .ok_or_else(|| anyhow::anyhow!("Missing table in schema"))?;
430
431 Ok(TableSchema {
433 keyspace: keyspace.to_string(),
434 table: table.to_string(),
435 columns: Vec::new(), partition_keys: Vec::new(),
437 clustering_keys: Vec::new(),
438 comments: HashMap::new(),
439 })
440}
441
442fn determine_column_kind(schema: &TableSchema, column_name: &str) -> String {
444 if schema.partition_keys.iter().any(|k| k.name == column_name) {
445 "PartitionKey".to_string()
446 } else if schema.clustering_keys.iter().any(|k| k.name == column_name) {
447 "ClusteringKey".to_string()
448 } else {
449 "Regular".to_string()
450 }
451}
452
453pub async fn perform_file_validation(
455 _file_path: &Path,
456 _reader: &SSTableReader,
457) -> Result<ValidationResult> {
458 Ok(ValidationResult {
460 is_valid: true,
461 errors: vec![],
462 warnings: vec!["File validation not yet fully implemented".to_string()],
463 total_blocks_checked: 0,
464 corrupted_blocks: 0,
465 })
466}
467
468pub async fn perform_directory_validation(directory: &SSTableDirectory) -> ValidationResult {
470 match directory.validate_all_generations() {
471 Ok(report) => ValidationResult {
472 is_valid: report.is_valid(),
473 errors: report.validation_errors.clone(),
474 warnings: report.toc_inconsistencies.clone(),
475 total_blocks_checked: 0,
476 corrupted_blocks: 0,
477 },
478 Err(e) => ValidationResult {
479 is_valid: false,
480 errors: vec![format!("Directory validation failed: {}", e)],
481 warnings: vec![],
482 total_blocks_checked: 0,
483 corrupted_blocks: 0,
484 },
485 }
486}
487
488fn find_data_file_in_directory(dir_path: &Path) -> Option<PathBuf> {
490 let patterns = ["*-Data.db", "*-big-Data.db", "nb-*-big-Data.db"];
491
492 for _pattern in &patterns {
493 if let Ok(entries) = std::fs::read_dir(dir_path) {
494 for entry in entries.flatten() {
495 let file_name = entry.file_name();
496 let file_name_str = file_name.to_string_lossy();
497
498 if file_name_str.ends_with("-Data.db") {
500 return Some(entry.path());
501 }
502 }
503 }
504 }
505
506 None
507}
508
509pub fn get_format_features(version: Option<&str>) -> Vec<String> {
511 match version {
512 Some("3.11") => vec![
513 "Legacy SSTable format (la)".to_string(),
514 "Basic compression support".to_string(),
515 "Simple bloom filters".to_string(),
516 "Traditional indexing".to_string(),
517 ],
518 Some("4.0") => vec![
519 "Modern compact format (mc)".to_string(),
520 "Improved compression algorithms".to_string(),
521 "Enhanced bloom filters".to_string(),
522 "Streaming support".to_string(),
523 "Better statistics".to_string(),
524 ],
525 Some("5.0") => vec![
526 "New big format (nb)".to_string(),
527 "Big Trie Index (bti) support".to_string(),
528 "Advanced compression".to_string(),
529 "Optimized I/O patterns".to_string(),
530 "Enhanced metadata".to_string(),
531 "Native ZSTD compression".to_string(),
532 ],
533 _ => vec!["Unknown format features".to_string()],
534 }
535}
536
537fn create_progress_bar(message: &str) -> ProgressBar {
539 let pb = ProgressBar::new_spinner();
540 pb.set_style(
541 ProgressStyle::default_spinner()
542 .template("{spinner:.green} [{elapsed_precise}] {msg}")
543 .unwrap(),
544 );
545 pb.set_message(message.to_string());
546 pb
547}
548
549pub async fn display_info_output(
551 metadata: SSTableInfoMetadata,
552 format: InfoOutputFormat,
553) -> Result<()> {
554 match format {
555 InfoOutputFormat::Text => display_text_format(&metadata).await,
556 InfoOutputFormat::Json => display_json_format(&metadata).await,
557 InfoOutputFormat::Csv => display_csv_format(&metadata).await,
558 }
559}
560
561async fn display_text_format(metadata: &SSTableInfoMetadata) -> Result<()> {
563 println!();
564 println!("š SSTable Analysis Results");
565 println!("===========================");
566
567 println!("š Path: {}", metadata.file_path.display());
569 println!(
570 "š Type: {}",
571 match metadata.file_type {
572 SSTableFileType::SingleFile => "Single SSTable File",
573 SSTableFileType::Directory => "SSTable Directory",
574 }
575 );
576 println!(
577 "š Size: {} bytes ({:.2} MB)",
578 metadata.file_size,
579 metadata.file_size as f64 / 1_048_576.0
580 );
581
582 if let Some(ref version) = metadata.detected_version {
584 println!("š Detected Version: {version}");
585 }
586 if let Some(ref version) = metadata.validated_version {
587 println!("ā
Cassandra Version: {version}");
588 }
589
590 println!("\nš Format Features:");
592 for feature in &metadata.format_features {
593 println!(" ⢠{feature}");
594 }
595
596 if let Some(ref bp_info) = metadata.bulletproof_info {
598 println!("\nš”ļø Bulletproof Reader Analysis:");
599 println!(" Format: {}", bp_info.format);
600 println!(" Generation: {}", bp_info.generation);
601 println!(" Detected Size: {} bytes", bp_info.detected_size);
602
603 if let Some(ref compression) = bp_info.compression_info {
604 println!(
605 " Compression: {} (chunk size: {})",
606 compression.algorithm, compression.chunk_length
607 );
608 }
609
610 if let Some(integrity) = bp_info.integrity_check {
611 println!(
612 " Integrity: {}",
613 if integrity {
614 "ā
Valid"
615 } else {
616 "ā Issues detected"
617 }
618 );
619 }
620
621 if let Some(ref sample) = bp_info.data_sample {
622 println!(" Data Entries: {} total", sample.total_entries);
623 if !sample.sample_entries.is_empty() {
624 println!(
625 " Sample Keys: {}",
626 sample
627 .sample_entries
628 .iter()
629 .take(3)
630 .cloned()
631 .collect::<Vec<_>>()
632 .join(", ")
633 );
634 }
635 }
636 }
637
638 if let Some(ref schema) = metadata.schema_info {
640 println!("\nš Schema Information:");
641 println!(" Keyspace: {}", schema.keyspace);
642 println!(" Table: {}", schema.table);
643 println!(" Columns: {}", schema.columns.len());
644 println!(" Partition Keys: {}", schema.partition_keys.join(", "));
645 if !schema.clustering_keys.is_empty() {
646 println!(" Clustering Keys: {}", schema.clustering_keys.join(", "));
647 }
648
649 if !schema.columns.is_empty() {
651 println!(" Column Details:");
652 for col in &schema.columns {
653 println!(" {} ({}) - {}", col.name, col.data_type, col.kind);
654 }
655 }
656 }
657
658 if let Some(ref dir_info) = metadata.directory_info {
660 println!("\nš Directory Information:");
661 println!(" Table Name: {}", dir_info.table_name);
662 println!(" Generations: {}", dir_info.generations);
663 println!(" Total Components: {}", dir_info.total_components);
664 println!(
665 " Valid: {}",
666 if dir_info.is_valid {
667 "ā
Yes"
668 } else {
669 "ā No"
670 }
671 );
672
673 if !dir_info.generation_info.is_empty() {
675 println!("\n š Generation Details:");
676 for generation in &dir_info.generation_info {
677 println!(
678 " Generation {}: {} format, {} bytes, {} components",
679 generation.generation,
680 generation.format,
681 generation.size,
682 generation.components.len()
683 );
684
685 for component in &generation.components {
686 println!(
687 " {} - {} bytes",
688 component.component_type, component.file_size
689 );
690 }
691 }
692 }
693 }
694
695 if metadata.entry_count > 0 || metadata.table_count > 0 {
697 println!("\nš SSTable Statistics:");
698 println!(" Entry Count: {}", metadata.entry_count);
699 println!(" Table Count: {}", metadata.table_count);
700 println!(" Block Count: {}", metadata.block_count);
701 println!(" Index Size: {} bytes", metadata.index_size);
702 println!(" Bloom Filter Size: {} bytes", metadata.bloom_filter_size);
703 println!(
704 " Compression Ratio: {:.2}%",
705 metadata.compression_ratio * 100.0
706 );
707 println!(" Cache Hit Rate: {:.2}%", metadata.cache_hit_rate * 100.0);
708 }
709
710 if let Some((ref path, ref status)) = metadata.statistics_file {
712 println!("\nš Statistics File:");
713 println!(" Path: {}", path.display());
714 println!(" Status: {status}");
715 }
716
717 if let Some(ref validation) = metadata.validation_result {
719 println!("\nš Validation Results:");
720 println!(
721 " Status: {}",
722 if validation.is_valid {
723 "ā
Valid"
724 } else {
725 "ā Invalid"
726 }
727 );
728
729 if validation.total_blocks_checked > 0 {
730 println!(" Blocks Checked: {}", validation.total_blocks_checked);
731 println!(" Corrupted Blocks: {}", validation.corrupted_blocks);
732 }
733
734 if !validation.errors.is_empty() {
735 println!(" Errors:");
736 for error in &validation.errors {
737 println!(" ā {error}");
738 }
739 }
740
741 if !validation.warnings.is_empty() {
742 println!(" Warnings:");
743 for warning in &validation.warnings {
744 println!(" ā ļø {warning}");
745 }
746 }
747 }
748
749 println!("\nā
Analysis Complete");
750 Ok(())
751}
752
753async fn display_json_format(metadata: &SSTableInfoMetadata) -> Result<()> {
755 let json = serde_json::to_string_pretty(metadata)?;
756 println!("{json}");
757 Ok(())
758}
759
760async fn display_csv_format(metadata: &SSTableInfoMetadata) -> Result<()> {
762 let mut wtr = csv::Writer::from_writer(std::io::stdout());
763
764 wtr.write_record([
766 "path",
767 "type",
768 "size_bytes",
769 "detected_version",
770 "cassandra_version",
771 "generations",
772 "components",
773 "entry_count",
774 "table_count",
775 "block_count",
776 "compression_ratio",
777 "cache_hit_rate",
778 "is_valid",
779 "errors",
780 "warnings",
781 ])?;
782
783 let file_type = match metadata.file_type {
785 SSTableFileType::SingleFile => "file",
786 SSTableFileType::Directory => "directory",
787 };
788
789 let generations = metadata
790 .directory_info
791 .as_ref()
792 .map(|d| d.generations.to_string())
793 .unwrap_or_else(|| "1".to_string());
794
795 let components = metadata
796 .directory_info
797 .as_ref()
798 .map(|d| d.total_components.to_string())
799 .unwrap_or_else(|| "1".to_string());
800
801 let entry_count = metadata.entry_count.to_string();
802 let table_count = metadata.table_count.to_string();
803 let block_count = metadata.block_count.to_string();
804 let compression_ratio = format!("{:.2}", metadata.compression_ratio * 100.0);
805 let cache_hit_rate = format!("{:.2}", metadata.cache_hit_rate * 100.0);
806
807 let is_valid = metadata
808 .validation_result
809 .as_ref()
810 .map(|v| v.is_valid.to_string())
811 .unwrap_or_else(|| "unknown".to_string());
812
813 let errors = metadata
814 .validation_result
815 .as_ref()
816 .map(|v| v.errors.join("; "))
817 .unwrap_or_default();
818
819 let warnings = metadata
820 .validation_result
821 .as_ref()
822 .map(|v| v.warnings.join("; "))
823 .unwrap_or_default();
824
825 wtr.write_record([
827 &metadata.file_path.display().to_string(),
828 file_type,
829 &metadata.file_size.to_string(),
830 metadata.detected_version.as_deref().unwrap_or("unknown"),
831 metadata.validated_version.as_deref().unwrap_or("unknown"),
832 &generations,
833 &components,
834 &entry_count,
835 &table_count,
836 &block_count,
837 &compression_ratio,
838 &cache_hit_rate,
839 &is_valid,
840 &errors,
841 &warnings,
842 ])?;
843
844 wtr.flush()?;
845 Ok(())
846}
847
848#[derive(Debug, Clone, serde::Serialize)]
852pub struct SSTableInfoMetadata {
853 pub file_path: PathBuf,
854 pub file_type: SSTableFileType,
855 pub file_size: u64,
856 pub detected_version: Option<String>,
857 pub validated_version: Option<String>,
858 pub format_features: Vec<String>,
859 pub schema_info: Option<SchemaInfo>,
860 pub validation_result: Option<ValidationResult>,
861 pub directory_info: Option<DirectoryInfo>,
862 pub bulletproof_info: Option<BulletproofInfo>,
863 pub statistics_file: Option<(PathBuf, String)>,
864 pub entry_count: u64,
866 pub table_count: u32,
867 pub block_count: u32,
868 pub index_size: u64,
869 pub bloom_filter_size: u64,
870 pub compression_ratio: f64,
871 pub cache_hit_rate: f64,
872}
873
874#[derive(Debug, Clone, serde::Serialize)]
876pub enum SSTableFileType {
877 SingleFile,
878 Directory,
879}
880
881#[derive(Debug, Clone, serde::Serialize)]
883pub struct SchemaInfo {
884 pub keyspace: String,
885 pub table: String,
886 pub columns: Vec<ColumnInfo>,
887 pub partition_keys: Vec<String>,
888 pub clustering_keys: Vec<String>,
889}
890
891#[derive(Debug, Clone, serde::Serialize)]
893pub struct ColumnInfo {
894 pub name: String,
895 pub data_type: String,
896 pub kind: String,
897}
898
899#[derive(Debug, Clone, serde::Serialize)]
901pub struct DirectoryInfo {
902 pub table_name: String,
903 pub generations: usize,
904 pub is_valid: bool,
905 pub total_components: usize,
906 pub generation_info: Vec<GenerationInfo>,
907}
908
909#[derive(Debug, Clone, serde::Serialize)]
911pub struct GenerationInfo {
912 pub generation: u32,
913 pub format: String,
914 pub size: u64,
915 pub components: Vec<ComponentInfo>,
916}
917
918#[derive(Debug, Clone, serde::Serialize)]
920pub struct ComponentInfo {
921 pub component_type: String,
922 pub file_path: PathBuf,
923 pub file_size: u64,
924}
925
926#[derive(Debug, Clone, serde::Serialize)]
928pub struct BulletproofInfo {
929 pub format: String,
930 pub generation: u32,
931 pub detected_size: u64,
932 pub compression_info: Option<CompressionInfo>,
933 pub integrity_check: Option<bool>,
934 pub data_sample: Option<DataSampleInfo>,
935}
936
937#[derive(Debug, Clone, serde::Serialize)]
939pub struct CompressionInfo {
940 pub algorithm: String,
941 pub chunk_length: u32,
942}
943
944#[derive(Debug, Clone, serde::Serialize)]
946pub struct DataSampleInfo {
947 pub total_entries: usize,
948 pub sample_entries: Vec<String>, }
950
951#[derive(Debug, Clone, serde::Serialize)]
953pub struct ValidationResult {
954 pub is_valid: bool,
955 pub errors: Vec<String>,
956 pub warnings: Vec<String>,
957 pub total_blocks_checked: usize,
958 pub corrupted_blocks: usize,
959}