1use anyhow::{Context, Result};
18use prettytable::{row, Table};
19use rusqlite::{params, Connection};
20use serde_json::{json, Value};
21use std::collections::HashMap;
22use std::error::Error;
23use std::fmt;
24use std::time::{Duration, Instant};
25
26#[derive(Debug, Clone)]
28pub enum OutputFormat {
29 Table,
30 Json,
31 Csv,
32}
33
34#[allow(dead_code)]
35#[derive(Debug)]
39#[allow(dead_code)]
40pub enum DisplayError {
41 DatabaseError(String),
42 QueryError(String),
43}
44
45impl fmt::Display for DisplayError {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 match self {
48 DisplayError::QueryError(msg) => write!(f, "Query error: {}", msg),
49 DisplayError::DatabaseError(msg) => write!(f, "Database error: {}", msg),
50 }
51 }
52}
53
54impl Error for DisplayError {}
55
56pub struct QueryOptions {
58 pub format: OutputFormat,
59 pub max_rows: Option<usize>,
60 pub show_timing: bool,
61}
62
63impl Default for QueryOptions {
64 fn default() -> Self {
65 Self {
66 format: OutputFormat::Table,
67 max_rows: Some(1000),
68 show_timing: true,
69 }
70 }
71}
72
73#[allow(dead_code)]
74#[allow(dead_code)]
78pub struct QueryCache {
79 results: HashMap<String, (Vec<Vec<String>>, Instant)>,
80 max_size: usize,
81 ttl: Duration,
82}
83
84#[allow(dead_code)]
85impl QueryCache {
86 pub fn new(max_size: usize, ttl: Duration) -> Self {
87 Self {
88 results: HashMap::new(),
89 max_size,
90 ttl,
91 }
92 }
93
94 pub fn get(&self, query: &str) -> Option<&Vec<Vec<String>>> {
95 if let Some((results, timestamp)) = self.results.get(query) {
96 if timestamp.elapsed() < self.ttl {
97 return Some(results);
98 }
99 }
100 None
101 }
102
103 pub fn insert(&mut self, query: String, results: Vec<Vec<String>>) {
104 if self.results.len() >= self.max_size {
106 let oldest_key = self
107 .results
108 .iter()
109 .min_by_key(|(_, (_, time))| time)
110 .map(|(key, _)| key.clone());
111
112 if let Some(key) = oldest_key {
113 self.results.remove(&key);
114 }
115 }
116
117 self.results.insert(query, (results, Instant::now()));
118 }
119
120 pub fn clear(&mut self) {
121 self.results.clear();
122 }
123}
124
125#[allow(dead_code)]
126#[allow(dead_code)]
130pub struct ProgressiveLoader {
131 batch_size: usize,
132 total_rows: usize,
133 loaded_rows: usize,
134 column_names: Vec<String>,
135 current_batch: Vec<Vec<String>>,
136}
137
138#[allow(dead_code)]
139impl ProgressiveLoader {
140 pub fn new(batch_size: usize, column_names: Vec<String>) -> Self {
141 Self {
142 batch_size,
143 total_rows: 0,
144 loaded_rows: 0,
145 column_names,
146 current_batch: Vec::new(),
147 }
148 }
149
150 pub fn add_row(&mut self, row: Vec<String>) {
151 self.current_batch.push(row);
152 self.loaded_rows += 1;
153
154 if self.current_batch.len() >= self.batch_size {
155 self.flush_batch();
156 }
157 }
158
159 pub fn flush_batch(&mut self) {
160 if !self.current_batch.is_empty() {
161 display_as_table(&self.column_names, &self.current_batch);
162 println!("Loaded {}/{} rows...", self.loaded_rows, self.total_rows);
163 self.current_batch.clear();
164 }
165 }
166
167 pub fn set_total_rows(&mut self, total: usize) {
168 self.total_rows = total;
169 }
170
171 pub fn is_complete(&self) -> bool {
172 self.loaded_rows >= self.total_rows
173 }
174}
175
176pub fn execute_sql(conn: &Connection, sql: &str, options: &QueryOptions) -> Result<()> {
191 let start_time = Instant::now();
192
193 let mut stmt = conn
195 .prepare(sql)
196 .context("Failed to prepare SQL statement")?;
197
198 let is_select = sql.trim().to_uppercase().starts_with("SELECT");
200
201 if is_select {
202 let column_names: Vec<String> = stmt
204 .column_names()
205 .iter()
206 .map(|name| name.to_string())
207 .collect();
208
209 let mut rows = stmt.query([]).context("Failed to execute SELECT query")?;
210
211 let mut all_rows = Vec::new();
213 let mut row_count = 0;
214
215 while let Some(row) = rows.next()? {
216 let mut row_values = Vec::new();
217 for i in 0..column_names.len() {
218 let value = match row.get_ref(i)? {
219 rusqlite::types::ValueRef::Null => "NULL".to_string(),
220 rusqlite::types::ValueRef::Integer(val) => val.to_string(),
221 rusqlite::types::ValueRef::Real(val) => val.to_string(),
222 rusqlite::types::ValueRef::Text(val) => {
223 String::from_utf8_lossy(val).to_string()
224 }
225 rusqlite::types::ValueRef::Blob(val) => {
226 format!("<binary data: {} bytes>", val.len())
227 }
228 };
229 row_values.push(value);
230 }
231 all_rows.push(row_values);
232 row_count += 1;
233
234 if let Some(limit) = options.max_rows {
235 if row_count >= limit {
236 break;
237 }
238 }
239 }
240
241 if !all_rows.is_empty() {
243 match options.format {
244 OutputFormat::Table => display_as_table(&column_names, &all_rows),
245 OutputFormat::Json => display_as_json(&column_names, &all_rows)?,
246 OutputFormat::Csv => display_as_csv(&column_names, &all_rows),
247 }
248 }
249
250 println!("{} row(s) returned", row_count);
251
252 if let Some(limit) = options.max_rows {
253 if row_count >= limit {
254 println!(
255 "(Limited to {} rows. Use '.limit 0' to show all rows)",
256 limit
257 );
258 }
259 }
260 } else {
261 let affected = stmt
263 .execute([])
264 .context("Failed to execute non-SELECT query")?;
265
266 println!("{} row(s) affected", affected);
267 }
268
269 if options.show_timing {
270 println!(
271 "Query executed in {:.3}ms",
272 start_time.elapsed().as_secs_f64() * 1000.0
273 );
274 }
275
276 Ok(())
277}
278
279fn display_as_table(column_names: &[String], rows: &[Vec<String>]) {
281 let mut table = Table::new();
282 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
283
284 let mut header_row = prettytable::Row::empty();
286 for col_name in column_names {
287 header_row.add_cell(prettytable::Cell::new(col_name).style_spec("b"));
288 }
289 table.add_row(header_row);
290
291 for row_values in rows {
293 let mut data_row = prettytable::Row::empty();
294 for value in row_values {
295 data_row.add_cell(prettytable::Cell::new(value));
296 }
297 table.add_row(data_row);
298 }
299
300 table.printstd();
301}
302
303fn display_as_json(column_names: &[String], rows: &[Vec<String>]) -> Result<()> {
308 let mut json_rows = Vec::new();
309
310 for row_values in rows {
311 let mut json_row = serde_json::Map::new();
312 for (i, value) in row_values.iter().enumerate() {
313 let json_value = if value == "NULL" {
314 Value::Null
315 } else if let Ok(int_val) = value.parse::<i64>() {
316 Value::Number(serde_json::Number::from(int_val))
317 } else if let Ok(float_val) = value.parse::<f64>() {
318 Value::Number(
319 serde_json::Number::from_f64(float_val)
320 .unwrap_or_else(|| serde_json::Number::from(0)),
321 )
322 } else {
323 Value::String(value.clone())
324 };
325 json_row.insert(column_names[i].clone(), json_value);
326 }
327 json_rows.push(Value::Object(json_row));
328 }
329
330 let output = json!({
331 "data": json_rows,
332 "columns": column_names,
333 "row_count": rows.len()
334 });
335
336 println!("{}", serde_json::to_string_pretty(&output)?);
337 Ok(())
338}
339
340fn display_as_csv(column_names: &[String], rows: &[Vec<String>]) {
344 println!("{}", column_names.join(","));
346
347 for row_values in rows {
349 let escaped_values: Vec<String> = row_values
350 .iter()
351 .map(|v| {
352 if v.contains(',') || v.contains('"') || v.contains('\n') {
353 format!("\"{}\"", v.replace('"', "\"\""))
354 } else {
355 v.clone()
356 }
357 })
358 .collect();
359 println!("{}", escaped_values.join(","));
360 }
361}
362
363pub fn show_table_schema(conn: &Connection, table_name: &str) -> Result<()> {
376 let mut check_stmt = conn
378 .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?")
379 .context("Failed to prepare statement for checking table existence")?;
380
381 let exists: bool = check_stmt
382 .exists(params![table_name])
383 .context(format!("Failed to check if table '{}' exists", table_name))?;
384
385 if !exists {
386 println!("Table '{}' does not exist.", table_name);
387 return Ok(());
388 }
389
390 let pragma_sql = format!("PRAGMA table_info({})", table_name);
392 let mut stmt = conn.prepare(&pragma_sql).context(format!(
393 "Failed to prepare statement for table schema: {}",
394 table_name
395 ))?;
396
397 let columns = stmt
398 .query_map(params![], |row| {
399 Ok((
400 row.get::<_, i32>(0)?, row.get::<_, String>(1)?, row.get::<_, String>(2)?, row.get::<_, bool>(3)?, row.get::<_, Option<String>>(4)?, row.get::<_, i32>(5)?, ))
407 })
408 .context(format!("Failed to query schema for table: {}", table_name))?;
409
410 let mut table = Table::new();
412 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
413 table.add_row(row![
414 "ID",
415 "Name",
416 "Type",
417 "Not Null",
418 "Default Value",
419 "Primary Key"
420 ]);
421
422 let mut has_columns = false;
423 for column_result in columns {
424 has_columns = true;
425 let (cid, name, type_name, not_null, default_value, pk) = column_result.context(
426 format!("Failed to read column info for table: {}", table_name),
427 )?;
428
429 let not_null_str = if not_null { "YES" } else { "NO" };
430 let pk_str = if pk > 0 { "YES" } else { "NO" };
431 let default_str = default_value.unwrap_or_else(|| "NULL".to_string());
432
433 table.add_row(row![
434 cid,
435 name,
436 type_name,
437 not_null_str,
438 default_str,
439 pk_str
440 ]);
441 }
442
443 if has_columns {
444 println!("Schema for table '{}':", table_name);
445 table.printstd();
446 } else {
447 println!("No columns found for table: {}", table_name);
448 }
449
450 Ok(())
451}
452
453pub fn show_all_schemas(conn: &Connection) -> Result<()> {
465 let mut stmt = conn
467 .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
468 .context("Failed to prepare statement for listing tables")?;
469
470 let table_names = stmt
471 .query_map(params![], |row| row.get::<_, String>(0))
472 .context("Failed to query tables")?;
473
474 let mut has_tables = false;
475 for (i, table_name_result) in table_names.enumerate() {
476 has_tables = true;
477 let table_name = table_name_result.context("Failed to read table name")?;
478 if i > 0 {
479 println!();
480 }
481 show_table_schema(conn, &table_name)?;
482 }
483
484 if !has_tables {
485 println!("No tables found in the database.");
486 }
487
488 Ok(())
489}
490
491pub fn show_database_info(conn: &Connection, db_path: &str) -> Result<()> {
504 println!("Database Information:");
505 println!(" Path: {}", db_path);
506
507 if let Ok(metadata) = std::fs::metadata(db_path) {
509 let size_mb = metadata.len() as f64 / (1024.0 * 1024.0);
510 println!(" Size: {:.2} MB", size_mb);
511 }
512
513 let version: String = conn.query_row("SELECT sqlite_version()", [], |row| row.get(0))?;
515 println!(" SQLite Version: {}", version);
516
517 let page_size: i64 = conn.query_row("PRAGMA page_size", [], |row| row.get(0))?;
519 let page_count: i64 = conn.query_row("PRAGMA page_count", [], |row| row.get(0))?;
520 println!(" Page Size: {} bytes", page_size);
521 println!(" Page Count: {}", page_count);
522
523 let mut stmt = conn.prepare(
525 "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'",
526 )?;
527 let table_names = stmt.query_map([], |row| row.get::<_, String>(0))?;
528
529 println!("\nTable Statistics:");
530 let mut total_rows = 0;
531
532 for table_name_result in table_names {
533 let table_name = table_name_result?;
534 let count_sql = format!("SELECT COUNT(*) FROM {}", table_name);
535 let row_count: i64 = conn.query_row(&count_sql, [], |row| row.get(0))?;
536 println!(" {}: {} rows", table_name, row_count);
537 total_rows += row_count;
538 }
539
540 println!(" Total Rows: {}", total_rows);
541
542 Ok(())
543}