contextdb_cli/
formatter.rs1use contextdb_core::Value;
2use contextdb_engine::QueryResult;
3
4pub fn format_query_result(result: &QueryResult) -> String {
5 format_query_result_with_empty_headers(result, false)
6}
7
8pub fn format_query_result_with_empty_headers(
9 result: &QueryResult,
10 show_empty_headers: bool,
11) -> String {
12 if result.columns.is_empty() || (result.rows.is_empty() && !show_empty_headers) {
13 return String::new();
14 }
15
16 let mut widths: Vec<usize> = result.columns.iter().map(|c| c.len()).collect();
17
18 let rows: Vec<Vec<String>> = result
19 .rows
20 .iter()
21 .map(|row| row.iter().map(value_to_string).collect())
22 .collect();
23
24 for row in &rows {
25 for (i, cell) in row.iter().enumerate() {
26 if i < widths.len() {
27 widths[i] = widths[i].max(cell.len());
28 }
29 }
30 }
31
32 let sep = widths
33 .iter()
34 .map(|w| format!("+{}", "-".repeat(*w + 2)))
35 .collect::<String>()
36 + "+";
37
38 let mut out = String::new();
39 out.push_str(&sep);
40 out.push('\n');
41
42 out.push('|');
43 for (i, col) in result.columns.iter().enumerate() {
44 out.push(' ');
45 out.push_str(&format!("{:<width$}", col, width = widths[i]));
46 out.push(' ');
47 out.push('|');
48 }
49 out.push('\n');
50
51 out.push_str(&sep);
52 out.push('\n');
53
54 for row in rows {
55 out.push('|');
56 for (i, cell) in row.iter().enumerate() {
57 out.push(' ');
58 out.push_str(&format!("{:<width$}", cell, width = widths[i]));
59 out.push(' ');
60 out.push('|');
61 }
62 out.push('\n');
63 }
64
65 out.push_str(&sep);
66 out
67}
68
69fn value_to_string(v: &Value) -> String {
70 match v {
71 Value::Null => "NULL".to_string(),
72 Value::Bool(b) => b.to_string(),
73 Value::Int64(i) => i.to_string(),
74 Value::Float64(f) => f.to_string(),
75 Value::Text(s) => s.clone(),
76 Value::Uuid(u) => u.to_string(),
77 Value::Timestamp(ts) => ts.to_string(),
78 Value::Json(j) => j.to_string(),
79 Value::Vector(vec) => format!("{:?}", vec),
80 Value::TxId(tx) => tx.0.to_string(),
81 }
82}