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, last_select_query: &std::sync::Arc<std::sync::Mutex<String>>) -> 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 mut last_query_guard = last_select_query.lock().unwrap();
203 last_query_guard.clear();
204 last_query_guard.push_str(sql);
205 }
206
207 if is_select {
208 let column_names: Vec<String> = stmt
210 .column_names()
211 .iter()
212 .map(|name| name.to_string())
213 .collect();
214
215 let mut rows = stmt.query([]).context("Failed to execute SELECT query")?;
216
217 let mut all_rows = Vec::new();
219 let mut row_count = 0;
220
221 while let Some(row) = rows.next()? {
222 let mut row_values = Vec::new();
223 for i in 0..column_names.len() {
224 let value = match row.get_ref(i)? {
225 rusqlite::types::ValueRef::Null => "NULL".to_string(),
226 rusqlite::types::ValueRef::Integer(val) => val.to_string(),
227 rusqlite::types::ValueRef::Real(val) => val.to_string(),
228 rusqlite::types::ValueRef::Text(val) => {
229 String::from_utf8_lossy(val).to_string()
230 }
231 rusqlite::types::ValueRef::Blob(val) => {
232 format!("<binary data: {} bytes>", val.len())
233 }
234 };
235 row_values.push(value);
236 }
237 all_rows.push(row_values);
238 row_count += 1;
239
240 if let Some(limit) = options.max_rows {
241 if row_count >= limit {
242 break;
243 }
244 }
245 }
246
247 if !all_rows.is_empty() {
249 match options.format {
250 OutputFormat::Table => display_as_table(&column_names, &all_rows),
251 OutputFormat::Json => display_as_json(&column_names, &all_rows)?,
252 OutputFormat::Csv => display_as_csv(&column_names, &all_rows),
253 }
254 }
255
256 println!("{} row(s) returned", row_count);
257
258 if let Some(limit) = options.max_rows {
259 if row_count >= limit {
260 println!(
261 "(Limited to {} rows. Use '.limit 0' to show all rows)",
262 limit
263 );
264 }
265 }
266 } else {
267 let affected = stmt
269 .execute([])
270 .context("Failed to execute non-SELECT query")?;
271
272 println!("{} row(s) affected", affected);
273 }
274
275 if options.show_timing {
276 println!(
277 "Query executed in {:.3}ms",
278 start_time.elapsed().as_secs_f64() * 1000.0
279 );
280 }
281
282 Ok(())
283}
284
285fn display_as_table(column_names: &[String], rows: &[Vec<String>]) {
287 let mut table = Table::new();
288 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
289
290 let mut header_row = prettytable::Row::empty();
292 for col_name in column_names {
293 header_row.add_cell(prettytable::Cell::new(col_name).style_spec("b"));
294 }
295 table.add_row(header_row);
296
297 for row_values in rows {
299 let mut data_row = prettytable::Row::empty();
300 for value in row_values {
301 data_row.add_cell(prettytable::Cell::new(value));
302 }
303 table.add_row(data_row);
304 }
305
306 table.printstd();
307}
308
309fn display_as_json(column_names: &[String], rows: &[Vec<String>]) -> Result<()> {
314 let mut json_rows = Vec::new();
315
316 for row_values in rows {
317 let mut json_row = serde_json::Map::new();
318 for (i, value) in row_values.iter().enumerate() {
319 let json_value = if value == "NULL" {
320 Value::Null
321 } else if let Ok(int_val) = value.parse::<i64>() {
322 Value::Number(serde_json::Number::from(int_val))
323 } else if let Ok(float_val) = value.parse::<f64>() {
324 Value::Number(
325 serde_json::Number::from_f64(float_val)
326 .unwrap_or_else(|| serde_json::Number::from(0)),
327 )
328 } else {
329 Value::String(value.clone())
330 };
331 json_row.insert(column_names[i].clone(), json_value);
332 }
333 json_rows.push(Value::Object(json_row));
334 }
335
336 let output = json!({
337 "data": json_rows,
338 "columns": column_names,
339 "row_count": rows.len()
340 });
341
342 println!("{}", serde_json::to_string_pretty(&output)?);
343 Ok(())
344}
345
346fn display_as_csv(column_names: &[String], rows: &[Vec<String>]) {
350 println!("{}", column_names.join(","));
352
353 for row_values in rows {
355 let escaped_values: Vec<String> = row_values
356 .iter()
357 .map(|v| {
358 if v.contains(',') || v.contains('"') || v.contains('\n') {
359 format!("\"{}\"", v.replace('"', "\"\""))
360 } else {
361 v.clone()
362 }
363 })
364 .collect();
365 println!("{}", escaped_values.join(","));
366 }
367}
368
369pub fn show_table_schema(conn: &Connection, table_name: &str) -> Result<()> {
382 let mut check_stmt = conn
384 .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?")
385 .context("Failed to prepare statement for checking table existence")?;
386
387 let exists: bool = check_stmt
388 .exists(params![table_name])
389 .context(format!("Failed to check if table '{}' exists", table_name))?;
390
391 if !exists {
392 println!("Table '{}' does not exist.", table_name);
393 return Ok(());
394 }
395
396 let pragma_sql = format!("PRAGMA table_info({})", table_name);
398 let mut stmt = conn.prepare(&pragma_sql).context(format!(
399 "Failed to prepare statement for table schema: {}",
400 table_name
401 ))?;
402
403 let columns = stmt
404 .query_map(params![], |row| {
405 Ok((
406 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)?, ))
413 })
414 .context(format!("Failed to query schema for table: {}", table_name))?;
415
416 let mut table = Table::new();
418 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
419 table.add_row(row![
420 "ID",
421 "Name",
422 "Type",
423 "Not Null",
424 "Default Value",
425 "Primary Key"
426 ]);
427
428 let mut has_columns = false;
429 for column_result in columns {
430 has_columns = true;
431 let (cid, name, type_name, not_null, default_value, pk) = column_result.context(
432 format!("Failed to read column info for table: {}", table_name),
433 )?;
434
435 let not_null_str = if not_null { "YES" } else { "NO" };
436 let pk_str = if pk > 0 { "YES" } else { "NO" };
437 let default_str = default_value.unwrap_or_else(|| "NULL".to_string());
438
439 table.add_row(row![
440 cid,
441 name,
442 type_name,
443 not_null_str,
444 default_str,
445 pk_str
446 ]);
447 }
448
449 if has_columns {
450 println!("Schema for table '{}':", table_name);
451 table.printstd();
452 } else {
453 println!("No columns found for table: {}", table_name);
454 }
455
456 Ok(())
457}
458
459pub fn show_all_schemas(conn: &Connection) -> Result<()> {
471 let mut stmt = conn
473 .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
474 .context("Failed to prepare statement for listing tables")?;
475
476 let table_names = stmt
477 .query_map(params![], |row| row.get::<_, String>(0))
478 .context("Failed to query tables")?;
479
480 let mut has_tables = false;
481 for (i, table_name_result) in table_names.enumerate() {
482 has_tables = true;
483 let table_name = table_name_result.context("Failed to read table name")?;
484 if i > 0 {
485 println!();
486 }
487 show_table_schema(conn, &table_name)?;
488 }
489
490 if !has_tables {
491 println!("No tables found in the database.");
492 }
493
494 Ok(())
495}
496
497pub fn show_database_info(conn: &Connection, db_path: &str) -> Result<()> {
510 println!("Database Information:");
511 println!(" Path: {}", db_path);
512
513 if let Ok(metadata) = std::fs::metadata(db_path) {
515 let size_mb = metadata.len() as f64 / (1024.0 * 1024.0);
516 println!(" Size: {:.2} MB", size_mb);
517 }
518
519 let version: String = conn.query_row("SELECT sqlite_version()", [], |row| row.get(0))?;
521 println!(" SQLite Version: {}", version);
522
523 let page_size: i64 = conn.query_row("PRAGMA page_size", [], |row| row.get(0))?;
525 let page_count: i64 = conn.query_row("PRAGMA page_count", [], |row| row.get(0))?;
526 println!(" Page Size: {} bytes", page_size);
527 println!(" Page Count: {}", page_count);
528
529 let mut stmt = conn.prepare(
531 "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'",
532 )?;
533 let table_names = stmt.query_map([], |row| row.get::<_, String>(0))?;
534
535 println!("\nTable Statistics:");
536 let mut total_rows = 0;
537
538 for table_name_result in table_names {
539 let table_name = table_name_result?;
540 let count_sql = format!("SELECT COUNT(*) FROM {}", table_name);
541 let row_count: i64 = conn.query_row(&count_sql, [], |row| row.get(0))?;
542 println!(" {}: {} rows", table_name, row_count);
543 total_rows += row_count;
544 }
545
546 println!(" Total Rows: {}", total_rows);
547
548 Ok(())
549}