use anyhow::{Result, bail}; use serde::{Deserialize, Serialize}; use std::time::Instant; const DEFAULT_SERVER: &str = "http://127.0.0.1:3307"; #[derive(Debug, Serialize)] struct QueryRequest { conn: String, sql: String, } #[derive(Debug, Deserialize, Serialize)] struct QueryResponse { columns: Vec, rows: Vec>>, #[serde(rename = "rowCount")] row_count: usize, #[serde(rename = "durationMs")] duration_ms: u64, } #[derive(Debug, Deserialize)] struct ErrorResponse { error: String, } #[derive(Debug, Deserialize)] struct ConnectionsResponse { connections: Vec, } #[derive(Debug, Deserialize)] struct ConnectionInfo { name: String, database: String, host: String, status: String, } /// CLI 客户端 pub struct Cli { server: String, } impl Cli { pub fn new(server: Option) -> Self { Self { server: server.unwrap_or_else(|| DEFAULT_SERVER.to_string()), } } /// 执行查询并输出结果 pub fn query(&self, conn: &str, sql: &str, format: Option<&str>) -> Result<()> { let start = Instant::now(); let body = serde_json::to_string(&QueryRequest { conn: conn.to_string(), sql: sql.to_string(), })?; let response = ureq::post(&format!("{}/query", self.server)) .set("Content-Type", "application/json") .send_string(&body); // ureq 2.x: 非 2xx 会返回 Err,但响应体在 error 中 let body = match response { Ok(r) => r.into_string()?, Err(ureq::Error::Status(_, resp)) => resp.into_string()?, Err(e) => bail!("HTTP error: {}", e), }; let total_ms = start.elapsed().as_millis(); // 解析响应 if let Ok(err) = serde_json::from_str::(&body) { eprintln!("Error: {}", err.error); return Ok(()); } let result: QueryResponse = serde_json::from_str(&body)?; // 输出结果 match format { Some("json") => self.print_json(&result), Some("csv") => self.print_csv(&result), Some("vertical") | Some("vert") => self.print_vertical(&result), _ => self.print_table(&result), } println!("\n{} rows in set ({}ms db, {}ms total)", result.row_count, result.duration_ms, total_ms); Ok(()) } /// 执行语句 (INSERT/UPDATE/DELETE) pub fn execute(&self, conn: &str, sql: &str) -> Result<()> { let start = Instant::now(); let body = serde_json::to_string(&QueryRequest { conn: conn.to_string(), sql: sql.to_string(), })?; let response = ureq::post(&format!("{}/execute", self.server)) .set("Content-Type", "application/json") .send_string(&body); // ureq 2.x: 非 2xx 会返回 Err,但响应体在 error 中 let body = match response { Ok(r) => r.into_string()?, Err(ureq::Error::Status(_, resp)) => resp.into_string()?, Err(e) => bail!("HTTP error: {}", e), }; let total_ms = start.elapsed().as_millis(); if let Ok(err) = serde_json::from_str::(&body) { eprintln!("Error: {}", err.error); return Ok(()); } #[derive(Deserialize)] struct ExecuteResponse { #[serde(rename = "affectedRows")] affected_rows: u64, #[serde(rename = "lastInsertId")] last_insert_id: u64, } let result: ExecuteResponse = serde_json::from_str(&body)?; println!("Query OK, {} rows affected ({}ms total)", result.affected_rows, total_ms); if result.last_insert_id > 0 { println!("Last insert ID: {}", result.last_insert_id); } Ok(()) } /// 列出连接 pub fn list_connections(&self) -> Result<()> { let response = ureq::get(&format!("{}/connections", self.server)) .call()?; let body = response.into_string()?; if let Ok(err) = serde_json::from_str::(&body) { bail!("{}", err.error); } let result: ConnectionsResponse = serde_json::from_str(&body)?; println!("Connections:"); println!("{:<15} {:<20} {:<40} {:<10}", "Name", "Database", "Host", "Status"); println!("{}", "-".repeat(85)); for conn in result.connections { println!("{:<15} {:<20} {:<40} {:<10}", conn.name, conn.database, conn.host, conn.status); } Ok(()) } /// 检查代理是否运行 pub fn check_server(&self) -> Result { match ureq::get(&format!("{}/health", self.server)).call() { Ok(_) => Ok(true), Err(_) => Ok(false), } } // 输出格式化方法 fn print_table(&self, result: &QueryResponse) { if result.columns.is_empty() { return; } // 计算列宽 let mut widths: Vec = result.columns.iter().map(|c| c.len()).collect(); for row in &result.rows { for (i, val) in row.iter().enumerate() { let len = val.as_ref().map(|s| s.len()).unwrap_or(4); // NULL widths[i] = widths[i].max(len); } } // 打印表头 let header: String = result.columns.iter() .enumerate() .map(|(i, c)| format!(" {:width$} ", c, width = widths[i])) .collect::>() .join("|"); println!("+{}+", widths.iter().map(|w| "-".repeat(*w + 2)).collect::>().join("+")); println!("|{}|", header); println!("+{}+", widths.iter().map(|w| "-".repeat(*w + 2)).collect::>().join("+")); // 打印数据行 for row in &result.rows { let line: String = row.iter() .enumerate() .map(|(i, val)| { match val { Some(s) => format!(" {:width$} ", s, width = widths[i]), None => format!(" {:width$} ", "NULL", width = widths[i]), } }) .collect::>() .join("|"); println!("|{}|", line); } println!("+{}+", widths.iter().map(|w| "-".repeat(*w + 2)).collect::>().join("+")); } fn print_json(&self, result: &QueryResponse) { // 极简格式:对象数组 let rows: Vec = result.rows.iter().map(|row| { let mut obj = serde_json::Map::new(); for (i, col) in result.columns.iter().enumerate() { let val = row.get(i) .and_then(|v| v.as_ref()) .map(|s| { // 尝试解析为数字 if let Ok(n) = s.parse::() { serde_json::Value::Number(n.into()) } else if let Ok(n) = s.parse::() { serde_json::Value::Number(serde_json::Number::from_f64(n).unwrap_or_else(|| serde_json::Number::from(0))) } else { serde_json::Value::String(s.clone()) } }) .unwrap_or(serde_json::Value::Null); obj.insert(col.clone(), val); } serde_json::Value::Object(obj) }).collect(); println!("{}", serde_json::to_string(&rows).unwrap_or_default()); } fn print_csv(&self, result: &QueryResponse) { // 打印表头 println!("{}", result.columns.join(",")); // 打印数据 for row in &result.rows { let line: String = row.iter() .map(|val| { match val { Some(s) => { if s.contains(',') || s.contains('"') || s.contains('\n') { format!("\"{}\"", s.replace('"', "\"\"")) } else { s.clone() } } None => "".to_string(), } }) .collect::>() .join(","); println!("{}", line); } } fn print_vertical(&self, result: &QueryResponse) { for (row_idx, row) in result.rows.iter().enumerate() { if row_idx > 0 { println!(); } println!("*************************** {}. row ***************************", row_idx + 1); for (col_idx, col) in result.columns.iter().enumerate() { let val = row.get(col_idx) .and_then(|v| v.as_ref()) .map(|s| s.as_str()) .unwrap_or("NULL"); println!("{:>20}: {}", col, val); } } } }