1use anyhow::{Context, Result};
2use prettytable::{row, Table};
3use rusqlite::{Connection, params};
4use std::time::{Instant, Duration};
5use serde_json::{json, Value};
6use std::collections::HashMap;
7use std::error::Error;
8use std::fmt;
9
10#[derive(Debug, Clone)]
11pub enum OutputFormat {
12 Table,
13 Json,
14 Csv,
15}
16
17#[derive(Debug)]
18#[allow(dead_code)]
19pub enum DisplayError {
20 DatabaseError(String),
21 QueryError(String),
22}
23
24impl fmt::Display for DisplayError {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 match self {
27 DisplayError::QueryError(msg) => write!(f, "Query error: {}", msg),
28 DisplayError::DatabaseError(msg) => write!(f, "Database error: {}", msg),
29 }
30 }
31}
32
33impl Error for DisplayError {}
34
35pub struct QueryOptions {
36 pub format: OutputFormat,
37 pub max_rows: Option<usize>,
38 pub show_timing: bool,
39}
40
41impl Default for QueryOptions {
42 fn default() -> Self {
43 Self {
44 format: OutputFormat::Table,
45 max_rows: Some(1000),
46 show_timing: true,
47 }
48 }
49}
50
51#[allow(dead_code)]
52pub struct QueryCache {
53 results: HashMap<String, (Vec<Vec<String>>, Instant)>,
54 max_size: usize,
55 ttl: Duration,
56}
57
58#[allow(dead_code)]
59impl QueryCache {
60 pub fn new(max_size: usize, ttl: Duration) -> Self {
61 Self {
62 results: HashMap::new(),
63 max_size,
64 ttl,
65 }
66 }
67
68 pub fn get(&self, query: &str) -> Option<&Vec<Vec<String>>> {
69 if let Some((results, timestamp)) = self.results.get(query) {
70 if timestamp.elapsed() < self.ttl {
71 return Some(results);
72 }
73 }
74 None
75 }
76
77 pub fn insert(&mut self, query: String, results: Vec<Vec<String>>) {
78 if self.results.len() >= self.max_size {
80 let oldest_key = self.results.iter()
81 .min_by_key(|(_, (_, time))| time)
82 .map(|(key, _)| key.clone());
83
84 if let Some(key) = oldest_key {
85 self.results.remove(&key);
86 }
87 }
88
89 self.results.insert(query, (results, Instant::now()));
90 }
91
92 pub fn clear(&mut self) {
93 self.results.clear();
94 }
95}
96
97#[allow(dead_code)]
98pub struct ProgressiveLoader {
99 batch_size: usize,
100 total_rows: usize,
101 loaded_rows: usize,
102 column_names: Vec<String>,
103 current_batch: Vec<Vec<String>>,
104}
105
106#[allow(dead_code)]
107impl ProgressiveLoader {
108 pub fn new(batch_size: usize, column_names: Vec<String>) -> Self {
109 Self {
110 batch_size,
111 total_rows: 0,
112 loaded_rows: 0,
113 column_names,
114 current_batch: Vec::new(),
115 }
116 }
117
118 pub fn add_row(&mut self, row: Vec<String>) {
119 self.current_batch.push(row);
120 self.loaded_rows += 1;
121
122 if self.current_batch.len() >= self.batch_size {
123 self.flush_batch();
124 }
125 }
126
127 pub fn flush_batch(&mut self) {
128 if !self.current_batch.is_empty() {
129 display_as_table(&self.column_names, &self.current_batch);
130 println!("Loaded {}/{} rows...", self.loaded_rows, self.total_rows);
131 self.current_batch.clear();
132 }
133 }
134
135 pub fn set_total_rows(&mut self, total: usize) {
136 self.total_rows = total;
137 }
138
139 pub fn is_complete(&self) -> bool {
140 self.loaded_rows >= self.total_rows
141 }
142}
143
144pub fn execute_sql(conn: &Connection, sql: &str, options: &QueryOptions) -> Result<()> {
146 let start_time = Instant::now();
147
148 let mut stmt = conn.prepare(sql)
150 .context("Failed to prepare SQL statement")?;
151
152 let is_select = sql.trim().to_uppercase().starts_with("SELECT");
154
155 if is_select {
156 let column_names: Vec<String> = stmt.column_names()
158 .iter()
159 .map(|name| name.to_string())
160 .collect();
161
162 let mut rows = stmt.query([])
163 .context("Failed to execute SELECT query")?;
164
165 let mut all_rows = Vec::new();
167 let mut row_count = 0;
168
169 while let Some(row) = rows.next()? {
170 let mut row_values = Vec::new();
171 for i in 0..column_names.len() {
172 let value = match row.get_ref(i)? {
173 rusqlite::types::ValueRef::Null => "NULL".to_string(),
174 rusqlite::types::ValueRef::Integer(val) => val.to_string(),
175 rusqlite::types::ValueRef::Real(val) => val.to_string(),
176 rusqlite::types::ValueRef::Text(val) => String::from_utf8_lossy(val).to_string(),
177 rusqlite::types::ValueRef::Blob(val) => format!("<binary data: {} bytes>", val.len()),
178 };
179 row_values.push(value);
180 }
181 all_rows.push(row_values);
182 row_count += 1;
183
184 if let Some(limit) = options.max_rows {
185 if row_count >= limit {
186 break;
187 }
188 }
189 }
190
191 if !all_rows.is_empty() {
193 match options.format {
194 OutputFormat::Table => display_as_table(&column_names, &all_rows),
195 OutputFormat::Json => display_as_json(&column_names, &all_rows)?,
196 OutputFormat::Csv => display_as_csv(&column_names, &all_rows),
197 }
198 }
199
200 println!("{} row(s) returned", row_count);
201
202 if let Some(limit) = options.max_rows {
203 if row_count >= limit {
204 println!("(Limited to {} rows. Use '.limit 0' to show all rows)", limit);
205 }
206 }
207 } else {
208 let affected = stmt.execute([])
210 .context("Failed to execute non-SELECT query")?;
211
212 println!("{} row(s) affected", affected);
213 }
214
215 if options.show_timing {
216 println!("Query executed in {:.3}ms", start_time.elapsed().as_secs_f64() * 1000.0);
217 }
218
219 Ok(())
220}
221
222fn display_as_table(column_names: &[String], rows: &[Vec<String>]) {
223 let mut table = Table::new();
224 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
225
226 let mut header_row = prettytable::Row::empty();
228 for col_name in column_names {
229 header_row.add_cell(prettytable::Cell::new(col_name).style_spec("b"));
230 }
231 table.add_row(header_row);
232
233 for row_values in rows {
235 let mut data_row = prettytable::Row::empty();
236 for value in row_values {
237 data_row.add_cell(prettytable::Cell::new(value));
238 }
239 table.add_row(data_row);
240 }
241
242 table.printstd();
243}
244
245fn display_as_json(column_names: &[String], rows: &[Vec<String>]) -> Result<()> {
246 let mut json_rows = Vec::new();
247
248 for row_values in rows {
249 let mut json_row = serde_json::Map::new();
250 for (i, value) in row_values.iter().enumerate() {
251 let json_value = if value == "NULL" {
252 Value::Null
253 } else if let Ok(int_val) = value.parse::<i64>() {
254 Value::Number(serde_json::Number::from(int_val))
255 } else if let Ok(float_val) = value.parse::<f64>() {
256 Value::Number(serde_json::Number::from_f64(float_val).unwrap_or_else(|| serde_json::Number::from(0)))
257 } else {
258 Value::String(value.clone())
259 };
260 json_row.insert(column_names[i].clone(), json_value);
261 }
262 json_rows.push(Value::Object(json_row));
263 }
264
265 let output = json!({
266 "data": json_rows,
267 "columns": column_names,
268 "row_count": rows.len()
269 });
270
271 println!("{}", serde_json::to_string_pretty(&output)?);
272 Ok(())
273}
274
275fn display_as_csv(column_names: &[String], rows: &[Vec<String>]) {
276 println!("{}", column_names.join(","));
278
279 for row_values in rows {
281 let escaped_values: Vec<String> = row_values.iter()
282 .map(|v| {
283 if v.contains(',') || v.contains('"') || v.contains('\n') {
284 format!("\"{}\"", v.replace('"', "\"\""))
285 } else {
286 v.clone()
287 }
288 })
289 .collect();
290 println!("{}", escaped_values.join(","));
291 }
292}
293
294pub fn show_table_schema(conn: &Connection, table_name: &str) -> Result<()> {
296 let mut check_stmt = conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?")
298 .context("Failed to prepare statement for checking table existence")?;
299
300 let exists: bool = check_stmt.exists(params![table_name])
301 .context(format!("Failed to check if table '{}' exists", table_name))?;
302
303 if !exists {
304 println!("Table '{}' does not exist.", table_name);
305 return Ok(());
306 }
307
308 let pragma_sql = format!("PRAGMA table_info({})", table_name);
310 let mut stmt = conn.prepare(&pragma_sql)
311 .context(format!("Failed to prepare statement for table schema: {}", table_name))?;
312
313 let columns = stmt.query_map(params![], |row| {
314 Ok((
315 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)?, ))
322 }).context(format!("Failed to query schema for table: {}", table_name))?;
323
324 let mut table = Table::new();
326 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
327 table.add_row(row!["ID", "Name", "Type", "Not Null", "Default Value", "Primary Key"]);
328
329 let mut has_columns = false;
330 for column_result in columns {
331 has_columns = true;
332 let (cid, name, type_name, not_null, default_value, pk) = column_result
333 .context(format!("Failed to read column info for table: {}", table_name))?;
334
335 let not_null_str = if not_null { "YES" } else { "NO" };
336 let pk_str = if pk > 0 { "YES" } else { "NO" };
337 let default_str = default_value.unwrap_or_else(|| "NULL".to_string());
338
339 table.add_row(row![cid, name, type_name, not_null_str, default_str, pk_str]);
340 }
341
342 if has_columns {
343 println!("Schema for table '{}':", table_name);
344 table.printstd();
345 } else {
346 println!("No columns found for table: {}", table_name);
347 }
348
349 Ok(())
350}
351
352pub fn show_all_schemas(conn: &Connection) -> Result<()> {
354 let mut stmt = conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
356 .context("Failed to prepare statement for listing tables")?;
357
358 let table_names = stmt.query_map(params![], |row| row.get::<_, String>(0))
359 .context("Failed to query tables")?;
360
361 let mut has_tables = false;
362 for (i, table_name_result) in table_names.enumerate() {
363 has_tables = true;
364 let table_name = table_name_result.context("Failed to read table name")?;
365 if i > 0 {
366 println!();
367 }
368 show_table_schema(conn, &table_name)?;
369 }
370
371 if !has_tables {
372 println!("No tables found in the database.");
373 }
374
375 Ok(())
376}
377
378pub fn show_database_info(conn: &Connection, db_path: &str) -> Result<()> {
380 println!("Database Information:");
381 println!(" Path: {}", db_path);
382
383 if let Ok(metadata) = std::fs::metadata(db_path) {
385 let size_mb = metadata.len() as f64 / (1024.0 * 1024.0);
386 println!(" Size: {:.2} MB", size_mb);
387 }
388
389 let version: String = conn.query_row("SELECT sqlite_version()", [], |row| row.get(0))?;
391 println!(" SQLite Version: {}", version);
392
393 let page_size: i64 = conn.query_row("PRAGMA page_size", [], |row| row.get(0))?;
395 let page_count: i64 = conn.query_row("PRAGMA page_count", [], |row| row.get(0))?;
396 println!(" Page Size: {} bytes", page_size);
397 println!(" Page Count: {}", page_count);
398
399 let mut stmt = conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")?;
401 let table_names = stmt.query_map([], |row| row.get::<_, String>(0))?;
402
403 println!("\nTable Statistics:");
404 let mut total_rows = 0;
405
406 for table_name_result in table_names {
407 let table_name = table_name_result?;
408 let count_sql = format!("SELECT COUNT(*) FROM {}", table_name);
409 let row_count: i64 = conn.query_row(&count_sql, [], |row| row.get(0))?;
410 println!(" {}: {} rows", table_name, row_count);
411 total_rows += row_count;
412 }
413
414 println!(" Total Rows: {}", total_rows);
415
416 Ok(())
417}