Skip to main content

cqlite_cli/repl/commands/
status.rs

1//! :status meta-command implementation
2//!
3//! Displays discovery and schema coverage status using DiscoveryService
4
5use anyhow::Result;
6use colored::Colorize;
7use std::path::Path;
8use std::sync::Arc;
9use tokio::sync::RwLock;
10
11#[cfg(feature = "state_machine")]
12use cqlite_core::discovery::{CoverageBadge, DiscoveryService};
13
14/// Execute the :status command
15///
16/// Displays:
17/// - Data directory and discovery timestamp
18/// - Keyspaces and tables found
19/// - Schema coverage counts and deltas
20/// - Version hints
21/// - Coverage badge (Green/Yellow/Red/Unknown)
22pub async fn execute_status(
23    data_dir: Option<&Path>,
24    schema_registry: Option<Arc<RwLock<cqlite_core::schema::registry::SchemaRegistry>>>,
25) -> Result<()> {
26    #[cfg(not(feature = "state_machine"))]
27    {
28        let _ = data_dir; // Suppress unused warning
29        let _ = schema_registry; // Suppress unused warning
30        return Err(anyhow::anyhow!(
31            "Status command requires state_machine feature. Rebuild with --features state_machine"
32        ));
33    }
34
35    #[cfg(feature = "state_machine")]
36    {
37        // Check if data_dir is set
38        let Some(data_dir) = data_dir else {
39            return Err(anyhow::anyhow!(
40                "Data directory not configured. Use :config data-dir <PATH>"
41            ));
42        };
43
44        // Create discovery service
45        let discovery_service = if let Some(registry) = schema_registry {
46            DiscoveryService::with_schema_registry(data_dir.to_path_buf(), None, registry)
47        } else {
48            DiscoveryService::new(data_dir.to_path_buf(), None)
49        };
50
51        // Scan and display results
52        println!("{}", "Scanning data directory...".dimmed());
53        let summary = discovery_service.scan().await?;
54
55        display_summary(&summary);
56
57        Ok(())
58    }
59}
60
61#[cfg(feature = "state_machine")]
62fn display_summary(summary: &cqlite_core::discovery::DiscoverySummary) {
63    use chrono::{DateTime, Local};
64
65    println!();
66    println!("{}", "=== Data Discovery Status ===".green().bold());
67    println!();
68
69    // Data directory and timestamp
70    println!(
71        "{} {}",
72        "Data Directory:".cyan(),
73        summary.data_dir.display()
74    );
75    let datetime: DateTime<Local> = summary.timestamp.into();
76    println!(
77        "{} {}",
78        "Discovery Time:".cyan(),
79        datetime.format("%Y-%m-%d %H:%M:%S")
80    );
81    println!();
82
83    // Keyspaces and tables
84    println!(
85        "{} {} keyspace(s), {} table(s)",
86        "Discovered:".cyan(),
87        summary.keyspaces.len(),
88        summary.tables.len()
89    );
90    println!(
91        "{} {} SSTable file(s)",
92        "SSTable Files:".cyan(),
93        summary.sstables_found
94    );
95    println!();
96
97    // List keyspaces if any
98    if !summary.keyspaces.is_empty() {
99        println!("{}", "Keyspaces:".green().bold());
100        for keyspace in &summary.keyspaces {
101            println!("  - {}", keyspace.yellow());
102        }
103        println!();
104    }
105
106    // List tables (first 10)
107    if !summary.tables.is_empty() {
108        println!("{} (showing first 10):", "Tables:".green().bold());
109        for table in summary.tables.iter().take(10) {
110            println!("  - {}", table.cyan());
111        }
112        if summary.tables.len() > 10 {
113            println!("  {} {} more...", "...".dimmed(), summary.tables.len() - 10);
114        }
115        println!();
116    }
117
118    // Version info
119    if let Some(ref version) = summary.resolved_version {
120        println!("{} {}", "Cassandra Version:".cyan(), version);
121    } else {
122        println!("{} {}", "Cassandra Version:".cyan(), "unknown".dimmed());
123    }
124    println!();
125
126    // Schema coverage (if available)
127    if let Some(ref coverage) = summary.coverage {
128        println!("{}", "Schema Coverage:".green().bold());
129
130        let coverage_pct = coverage.coverage_percentage();
131        println!(
132            "  {} {}%",
133            "Coverage:".cyan(),
134            format!("{:.1}", coverage_pct).bold()
135        );
136
137        println!(
138            "  {} {}",
139            "Tables with schema:".cyan(),
140            coverage.tables_with_schema
141        );
142
143        if !coverage.tables_missing_schema.is_empty() {
144            println!(
145                "  {} {} (showing first 5):",
146                "Tables missing schema:".yellow(),
147                coverage.tables_missing_schema.len()
148            );
149            for table in coverage.tables_missing_schema.iter().take(5) {
150                println!("    - {}", table.yellow());
151            }
152            if coverage.tables_missing_schema.len() > 5 {
153                println!(
154                    "    {} {} more...",
155                    "...".dimmed(),
156                    coverage.tables_missing_schema.len() - 5
157                );
158            }
159            println!(
160                "  {}",
161                "Hint: Load schemas with :schema load <PATH>".dimmed()
162            );
163        }
164
165        if !coverage.schemas_without_data.is_empty() {
166            println!(
167                "  {} {} (showing first 5):",
168                "Schemas without data:".yellow(),
169                coverage.schemas_without_data.len()
170            );
171            for schema in coverage.schemas_without_data.iter().take(5) {
172                println!("    - {}", schema.dimmed());
173            }
174            if coverage.schemas_without_data.len() > 5 {
175                println!(
176                    "    {} {} more...",
177                    "...".dimmed(),
178                    coverage.schemas_without_data.len() - 5
179                );
180            }
181        }
182        println!();
183    } else {
184        println!(
185            "{}",
186            "Schema coverage: Not available (no schemas loaded)".yellow()
187        );
188        println!("{}", "Hint: Load schemas with :schema load <PATH>".dimmed());
189        println!();
190    }
191
192    // Coverage badge
193    let (badge_symbol, badge_text) = match summary.badge {
194        CoverageBadge::Green => ("✅", "Green (≥95% coverage)".green()),
195        CoverageBadge::Yellow => ("⚠️ ", "Yellow (50-95% coverage)".yellow()),
196        CoverageBadge::Red => ("❌", "Red (<50% coverage or critical errors)".red()),
197        CoverageBadge::Unknown => ("❓", "Unknown (no schema loaded)".dimmed()),
198    };
199
200    println!(
201        "{} {} {}",
202        "Status:".cyan().bold(),
203        badge_symbol,
204        badge_text
205    );
206    println!();
207}