ferro_cli/commands/
db_query.rs1use console::style;
4use sea_orm::{ConnectionTrait, Database, DbBackend, Statement};
5use std::env;
6
7pub fn run(query: String) {
8 dotenvy::dotenv().ok();
10
11 let database_url = match env::var("DATABASE_URL") {
12 Ok(url) => url,
13 Err(_) => {
14 eprintln!(
15 "{} DATABASE_URL not set in .env",
16 style("Error:").red().bold()
17 );
18 std::process::exit(1);
19 }
20 };
21
22 let rt = tokio::runtime::Runtime::new().unwrap();
24 rt.block_on(async {
25 execute_query(&database_url, &query).await;
26 });
27}
28
29async fn execute_query(database_url: &str, query: &str) {
30 let is_sqlite = database_url.starts_with("sqlite");
31 let backend = if is_sqlite {
32 DbBackend::Sqlite
33 } else {
34 DbBackend::Postgres
35 };
36
37 let db = match Database::connect(database_url).await {
39 Ok(db) => db,
40 Err(e) => {
41 eprintln!(
42 "{} Failed to connect to database: {}",
43 style("Error:").red().bold(),
44 e
45 );
46 std::process::exit(1);
47 }
48 };
49
50 let result = db
52 .query_all(Statement::from_string(backend, query.to_string()))
53 .await;
54
55 match result {
56 Ok(rows) => {
57 if rows.is_empty() {
58 println!("{}", style("Empty result set").dim());
59 return;
60 }
61
62 print_results(&rows);
65 }
66 Err(e) => {
67 eprintln!("{} Query failed: {}", style("Error:").red().bold(), e);
68 std::process::exit(1);
69 }
70 }
71}
72
73fn print_results(rows: &[sea_orm::QueryResult]) {
74 if rows.is_empty() {
75 return;
76 }
77
78 let mut table_data: Vec<Vec<String>> = Vec::new();
81
82 for row in rows {
83 let mut row_data: Vec<String> = Vec::new();
84
85 for i in 0..20 {
87 if let Ok(val) = row.try_get_by_index::<i32>(i) {
89 row_data.push(val.to_string());
90 } else if let Ok(val) = row.try_get_by_index::<i64>(i) {
91 row_data.push(val.to_string());
92 } else if let Ok(val) = row.try_get_by_index::<String>(i) {
93 row_data.push(val);
94 } else if let Ok(val) = row.try_get_by_index::<bool>(i) {
95 row_data.push(val.to_string());
96 } else if let Ok(val) = row.try_get_by_index::<f64>(i) {
97 row_data.push(val.to_string());
98 } else if let Ok(val) = row.try_get_by_index::<Option<String>>(i) {
99 row_data.push(val.unwrap_or_else(|| "NULL".to_string()));
100 } else if let Ok(val) = row.try_get_by_index::<Option<i32>>(i) {
101 row_data.push(
102 val.map(|v| v.to_string())
103 .unwrap_or_else(|| "NULL".to_string()),
104 );
105 } else {
106 break;
108 }
109 }
110
111 if !row_data.is_empty() {
112 table_data.push(row_data);
113 }
114 }
115
116 if table_data.is_empty() {
117 println!("{}", style("No data to display").dim());
118 return;
119 }
120
121 let num_cols = table_data.iter().map(|r| r.len()).max().unwrap_or(0);
123 let mut col_widths: Vec<usize> = vec![0; num_cols];
124
125 for row in &table_data {
126 for (i, cell) in row.iter().enumerate() {
127 if i < col_widths.len() {
128 col_widths[i] = col_widths[i].max(cell.len()).max(3);
129 }
130 }
131 }
132
133 let separator: String = col_widths
135 .iter()
136 .map(|w| "-".repeat(*w + 2))
137 .collect::<Vec<_>>()
138 .join("+");
139 println!("+{separator}+");
140
141 for row in &table_data {
143 let formatted: Vec<String> = row
144 .iter()
145 .enumerate()
146 .map(|(i, cell)| {
147 let width = col_widths.get(i).copied().unwrap_or(10);
148 format!(" {cell:width$} ")
149 })
150 .collect();
151 println!("|{}|", formatted.join("|"));
152 }
153
154 println!("+{separator}+");
155 println!("\n{} {} row(s)", style("→").cyan(), table_data.len());
156}
157
158#[cfg(test)]
159mod tests {
160 #[test]
161 fn test_detect_sqlite_backend() {
162 let url = "sqlite://test.db";
163 assert!(url.starts_with("sqlite"));
164 }
165
166 #[test]
167 fn test_detect_postgres_backend() {
168 let url = "postgres://localhost/test";
169 assert!(!url.starts_with("sqlite"));
170 }
171
172 #[test]
173 fn test_column_width_calculation() {
174 let table_data = vec![
175 vec!["id".to_string(), "name".to_string()],
176 vec!["1".to_string(), "Alice".to_string()],
177 vec!["2".to_string(), "Bob".to_string()],
178 ];
179
180 let num_cols = table_data.iter().map(|r| r.len()).max().unwrap_or(0);
181 let mut col_widths: Vec<usize> = vec![0; num_cols];
182
183 for row in &table_data {
184 for (i, cell) in row.iter().enumerate() {
185 if i < col_widths.len() {
186 col_widths[i] = col_widths[i].max(cell.len()).max(3);
187 }
188 }
189 }
190
191 assert_eq!(col_widths[0], 3); assert_eq!(col_widths[1], 5); }
194
195 #[test]
196 fn test_separator_generation() {
197 let col_widths = [3, 5, 10];
198 let separator: String = col_widths
199 .iter()
200 .map(|w| "-".repeat(*w + 2))
201 .collect::<Vec<_>>()
202 .join("+");
203
204 assert_eq!(separator, "-----+-------+------------");
205 }
206
207 #[test]
208 fn test_row_formatting() {
209 let row = ["1".to_string(), "Alice".to_string()];
210 let col_widths = [3, 10];
211
212 let formatted: Vec<String> = row
213 .iter()
214 .enumerate()
215 .map(|(i, cell)| {
216 let width = col_widths.get(i).copied().unwrap_or(10);
217 format!(" {cell:width$} ")
218 })
219 .collect();
220
221 assert_eq!(formatted[0], " 1 ");
222 assert_eq!(formatted[1], " Alice ");
223 }
224}