1use crate::cli_types::AdminCommands;
2use anyhow::Result;
3#[cfg(feature = "state_machine")]
4use chrono;
5use cqlite_core::Database;
6
7#[cfg(feature = "state_machine")]
8pub async fn handle_admin_command(database: &Database, command: AdminCommands) -> Result<()> {
9 match command {
10 AdminCommands::Info => show_database_info(database).await,
11 AdminCommands::Compact { force: _ } => compact_database(database).await,
12 AdminCommands::Backup {
13 destination,
14 compression: _,
15 } => backup_database(database, &destination).await,
16 AdminCommands::Restore { backup, force: _ } => restore_database(database, &backup).await,
17 }
18}
19
20#[cfg(not(feature = "state_machine"))]
21pub async fn handle_admin_command(_database: &Database, _command: AdminCommands) -> Result<()> {
22 Err(anyhow::anyhow!(
23 "Admin commands requiring query execution are not available in M1.\n\
24 Build with --features state_machine or use SSTableReader directly.\n\
25 See CLAUDE.md for M1 API examples."
26 ))
27}
28
29#[cfg(feature = "state_machine")]
30async fn show_database_info(database: &Database) -> Result<()> {
31 println!("Database Information:");
32
33 match database.stats().await {
35 Ok(stats) => {
36 println!("Storage Engine Stats:");
37 println!(
38 " - SSTable count: {}",
39 stats.storage_stats.sstables.sstable_count
40 );
41 println!(
42 " - Total entries: {}",
43 stats.storage_stats.sstables.total_entries
44 );
45
46 println!("Memory Stats:");
47 println!(
48 " - Total memory used: {} bytes",
49 stats.memory_stats.total_memory_used
50 );
51 println!(
52 " - Block cache hits: {}",
53 stats.memory_stats.block_cache_hits
54 );
55
56 #[cfg(feature = "state_machine")]
57 {
58 println!("Query Engine Stats:");
59 println!(" - Total queries: {}", stats.query_stats.total_queries);
60 println!(
61 " - Average execution time: {} μs",
62 stats.query_stats.avg_execution_time_us
63 );
64 }
65 }
66 Err(e) => {
67 println!("Failed to get database statistics: {}", e);
68 }
69 }
70
71 Ok(())
72}
73
74#[cfg(all(feature = "state_machine", feature = "experimental"))]
75async fn compact_database(database: &Database) -> Result<()> {
76 println!("Starting database compaction...");
77
78 match database.compact().await {
79 Ok(_) => {
80 println!("Database compaction successfully");
81 }
82 Err(e) => {
83 println!("Database compaction failed: {}", e);
84 return Err(anyhow::anyhow!("Compaction failed: {}", e));
85 }
86 }
87
88 Ok(())
89}
90
91#[cfg(all(feature = "state_machine", not(feature = "experimental")))]
92async fn compact_database(_database: &Database) -> Result<()> {
93 Err(anyhow::anyhow!(
94 "Database compaction requires the 'experimental' feature.\n\
95 Build with --features experimental to enable write operations."
96 ))
97}
98
99#[cfg(feature = "state_machine")]
100async fn backup_database(database: &Database, output: &std::path::Path) -> Result<()> {
101 use indicatif::{ProgressBar, ProgressStyle};
102 use serde_json;
103 use std::fs::File;
104 use std::io::{BufWriter, Write};
105
106 println!("Backing up database to {}", output.display());
107
108 if let Some(parent) = output.parent() {
110 std::fs::create_dir_all(parent)
111 .map_err(|e| anyhow::anyhow!("Failed to create backup directory: {}", e))?;
112 }
113
114 let backup_file =
116 File::create(output).map_err(|e| anyhow::anyhow!("Failed to create backup file: {}", e))?;
117 let mut writer = BufWriter::new(backup_file);
118
119 let stats = match database.stats().await {
121 Ok(stats) => stats,
122 Err(e) => {
123 println!("Warning: Could not get database statistics: {}", e);
124 return create_basic_backup(database, &mut writer, output).await;
126 }
127 };
128
129 let total_entries = stats.storage_stats.sstables.total_entries;
131 let pb = ProgressBar::new(total_entries);
132 pb.set_style(
133 ProgressStyle::default_bar()
134 .template(
135 "Backing up [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} entries ({eta})",
136 )
137 .unwrap()
138 .progress_chars("=>-"),
139 );
140
141 let backup_metadata = serde_json::json!({
143 "version": "1.0",
144 "timestamp": chrono::Utc::now().to_rfc3339(),
145 "cqlite_version": env!("CARGO_PKG_VERSION"),
146 "total_entries": total_entries,
147 "sstable_entries": stats.storage_stats.sstables.total_entries,
148 "sstable_count": stats.storage_stats.sstables.sstable_count
149 });
150
151 writeln!(writer, "# CQLite Database Backup")?;
153 writeln!(writer, "# {}", backup_metadata.to_string())?;
154 writeln!(writer)?;
155
156 let mut processed_entries = 0;
157
158 match database
161 .execute(
162 "SELECT keyspace_name, table_name FROM system.tables WHERE keyspace_name != 'system'",
163 )
164 .await
165 {
166 Ok(tables_result) => {
167 for table_row in &tables_result.rows {
168 if let (Some(keyspace), Some(table)) =
169 (table_row.get("keyspace_name"), table_row.get("table_name"))
170 {
171 let create_table_query = format!("DESCRIBE TABLE {}.{}", keyspace, table);
173 match database.execute(&create_table_query).await {
174 Ok(schema_result) => {
175 writeln!(writer, "-- Schema for {}.{}", keyspace, table)?;
176 for schema_row in &schema_result.rows {
177 if let Some(ddl) = schema_row.get("create_statement") {
178 writeln!(writer, "{}", ddl)?;
179 }
180 }
181 writeln!(writer)?;
182 }
183 Err(_) => {
184 writeln!(
186 writer,
187 "-- Table: {}.{} (schema extraction failed)",
188 keyspace, table
189 )?;
190 writeln!(
191 writer,
192 "-- CREATE TABLE {}.{} (...); -- Please recreate manually",
193 keyspace, table
194 )?;
195 writeln!(writer)?;
196 }
197 }
198
199 let select_query = format!("SELECT * FROM {}.{}", keyspace, table);
201 match database.execute(&select_query).await {
202 Ok(data_result) => {
203 writeln!(writer, "-- Data for {}.{}", keyspace, table)?;
204 for data_row in &data_result.rows {
205 let columns: Vec<String> = data_row.column_names();
207 let values: Vec<String> = columns
208 .iter()
209 .map(|col| {
210 data_row
211 .get(col)
212 .map(|v| format!("'{}'", v)) .unwrap_or_else(|| "NULL".to_string())
214 })
215 .collect();
216
217 let insert_stmt = format!(
218 "INSERT INTO {}.{} ({}) VALUES ({});",
219 keyspace,
220 table,
221 columns.join(", "),
222 values.join(", ")
223 );
224 writeln!(writer, "{}", insert_stmt)?;
225
226 processed_entries += 1;
227 pb.set_position(processed_entries);
228 }
229 writeln!(writer)?;
230 }
231 Err(e) => {
232 writeln!(
233 writer,
234 "-- Error exporting data for {}.{}: {}",
235 keyspace, table, e
236 )?;
237 }
238 }
239 }
240 }
241 }
242 Err(_) => {
243 println!("Warning: Could not access system tables, creating basic backup");
244 return create_basic_backup(database, &mut writer, output).await;
245 }
246 }
247
248 pb.finish_with_message("Backup completed");
249 writer.flush()?;
250
251 let file_size = std::fs::metadata(output)?.len();
252 println!("✓ Database backup completed successfully");
253 println!(" Backup file: {}", output.display());
254 println!(" Entries exported: {}", processed_entries);
255 println!(" File size: {:.2} MB", file_size as f64 / 1_048_576.0);
256
257 Ok(())
258}
259
260#[cfg(feature = "state_machine")]
261async fn create_basic_backup(
262 _database: &Database,
263 writer: &mut std::io::BufWriter<std::fs::File>,
264 output: &std::path::Path,
265) -> Result<()> {
266 use std::io::Write;
267
268 writeln!(writer, "# CQLite Database Backup (Basic Mode)")?;
270 writeln!(writer, "# Timestamp: {}", chrono::Utc::now().to_rfc3339())?;
271 writeln!(writer, "# Version: {}", env!("CARGO_PKG_VERSION"))?;
272 writeln!(writer)?;
273 writeln!(
274 writer,
275 "-- Note: This is a basic backup. Full schema and data export requires system table access."
276 )?;
277 writeln!(
278 writer,
279 "-- Please use manual CQL queries to restore your data."
280 )?;
281
282 writer.flush()?;
283
284 let file_size = std::fs::metadata(output)?.len();
285 println!("✓ Basic database backup completed");
286 println!(" Backup file: {}", output.display());
287 println!(" File size: {} bytes", file_size);
288 println!(
289 " Note: This backup contains minimal information. Consider upgrading core library for full backup support."
290 );
291
292 Ok(())
293}
294
295#[cfg(feature = "state_machine")]
296async fn restore_database(database: &Database, input: &std::path::Path) -> Result<()> {
297 use indicatif::{ProgressBar, ProgressStyle};
298 use std::fs::File;
299 use std::io::{BufRead, BufReader};
300
301 println!("Restoring database from {}", input.display());
302
303 if !input.exists() {
305 return Err(anyhow::anyhow!(
306 "Backup file not found: {}",
307 input.display()
308 ));
309 }
310
311 let backup_file =
313 File::open(input).map_err(|e| anyhow::anyhow!("Failed to open backup file: {}", e))?;
314 let reader = BufReader::new(backup_file);
315
316 let total_lines = reader.lines().count() as u64;
318
319 let backup_file = File::open(input)?;
321 let reader = BufReader::new(backup_file);
322
323 let pb = ProgressBar::new(total_lines);
325 pb.set_style(
326 ProgressStyle::default_bar()
327 .template(
328 "Restoring [{elapsed_precise}] [{bar:40.green/blue}] {pos}/{len} lines ({eta})",
329 )
330 .unwrap()
331 .progress_chars("=>-"),
332 );
333
334 let mut executed_statements = 0;
335 let mut errors = 0;
336 let mut line_number = 0;
337 let mut current_statement = String::new();
338
339 let mut backup_version: Option<String> = None;
341 let mut backup_timestamp: Option<String> = None;
342
343 for line_result in reader.lines() {
344 line_number += 1;
345 pb.set_position(line_number);
346
347 let line = line_result
348 .map_err(|e| anyhow::anyhow!("Error reading line {}: {}", line_number, e))?;
349
350 let trimmed_line = line.trim();
351
352 if trimmed_line.is_empty() {
354 continue;
355 }
356
357 if trimmed_line.starts_with('#') {
358 if trimmed_line.contains("\"version\":") {
360 if let Some(start) = trimmed_line.find('{') {
362 let json_str = &trimmed_line[start..];
363 if let Ok(metadata) = serde_json::from_str::<serde_json::Value>(json_str) {
364 backup_version = metadata["version"].as_str().map(|s| s.to_string());
365 backup_timestamp = metadata["timestamp"].as_str().map(|s| s.to_string());
366 }
367 }
368 }
369 continue;
370 }
371
372 if trimmed_line.starts_with("--") {
373 continue;
374 }
375
376 current_statement.push_str(&line);
378 current_statement.push(' ');
379
380 if trimmed_line.ends_with(';') {
382 let statement = current_statement.trim().trim_end_matches(';');
383
384 if !statement.is_empty() {
385 match database.execute(statement).await {
387 Ok(_result) => {
388 executed_statements += 1;
389 if line_number % 100 == 0 {
390 pb.set_message(format!("Executed {} statements", executed_statements));
391 }
392
393 if statement.to_uppercase().starts_with("CREATE") {
395 println!(
396 "✓ Executed: {}",
397 statement.chars().take(50).collect::<String>() + "..."
398 );
399 }
400 }
401 Err(e) => {
402 errors += 1;
403 eprintln!(
404 "❌ Error executing statement at line {}: {}",
405 line_number, e
406 );
407 eprintln!(
408 " Statement: {}",
409 statement.chars().take(100).collect::<String>() + "..."
410 );
411
412 if statement.to_uppercase().contains("CREATE KEYSPACE")
414 || statement.to_uppercase().contains("CREATE TABLE")
415 {
416 println!(
417 " ⚠️ Schema error - continuing but data integrity may be affected"
418 );
419 }
420 }
421 }
422 }
423
424 current_statement.clear();
425 }
426 }
427
428 if !current_statement.trim().is_empty() {
430 let statement = current_statement.trim();
431 println!(
432 "⚠️ Warning: Incomplete statement at end of file: {}",
433 statement.chars().take(50).collect::<String>() + "..."
434 );
435 }
436
437 pb.finish_with_message("Restore completed");
438
439 println!("\n📊 Restore Summary:");
441 if let Some(version) = backup_version {
442 println!(" Backup version: {}", version);
443 }
444 if let Some(timestamp) = backup_timestamp {
445 println!(" Backup created: {}", timestamp);
446 }
447 println!(" Total lines processed: {}", line_number);
448 println!(" Statements executed: {}", executed_statements);
449
450 if errors > 0 {
451 println!(" ❌ Errors encountered: {}", errors);
452 println!(" ⚠️ Database restore completed with errors. Please verify data integrity.");
453
454 if errors > executed_statements / 2 {
456 return Err(anyhow::anyhow!(
457 "Restore failed: too many errors ({} errors out of {} statements)",
458 errors,
459 executed_statements
460 ));
461 }
462 } else {
463 println!(" ✅ Database restore completed successfully!");
464 }
465
466 Ok(())
467}