systemprompt_database/services/
display.rs1use std::io::Write;
16
17use crate::models::{ColumnInfo, DatabaseInfo, QueryResult, TableInfo};
18
19pub trait DatabaseCliDisplay {
20 fn display_with_cli(&self);
21}
22
23pub fn report_write_failure(sink: &'static str, error: &std::io::Error) {
24 if error.kind() == std::io::ErrorKind::BrokenPipe {
25 return;
26 }
27 tracing::warn!(sink, error = %error, "Display sink write failed");
28}
29
30fn stdout_writeln(args: std::fmt::Arguments<'_>) {
31 if let Err(error) = writeln!(std::io::stdout(), "{args}") {
32 report_write_failure("stdout", &error);
33 }
34}
35
36impl DatabaseCliDisplay for Vec<TableInfo> {
37 fn display_with_cli(&self) {
38 if self.is_empty() {
39 stdout_writeln(format_args!("No tables found"));
40 } else {
41 stdout_writeln(format_args!("Tables:"));
42 for table in self {
43 stdout_writeln(format_args!(" {} (rows: {})", table.name, table.row_count));
44 }
45 }
46 }
47}
48
49impl DatabaseCliDisplay for (Vec<ColumnInfo>, i64) {
50 fn display_with_cli(&self) {
51 let (columns, _) = self;
52 stdout_writeln(format_args!("Columns:"));
53 for col in columns {
54 let default_display = col
55 .default
56 .as_deref()
57 .map_or_else(String::new, |d| format!("DEFAULT {d}"));
58
59 stdout_writeln(format_args!(
60 " {} {} {} {} {}",
61 col.name,
62 col.data_type,
63 if col.nullable { "NULL" } else { "NOT NULL" },
64 if col.primary_key { "PK" } else { "" },
65 default_display
66 ));
67 }
68 }
69}
70
71impl DatabaseCliDisplay for DatabaseInfo {
72 fn display_with_cli(&self) {
73 stdout_writeln(format_args!("Database Info:"));
74 stdout_writeln(format_args!(" Path: {}", self.path));
75 stdout_writeln(format_args!(" Version: {}", self.version));
76 stdout_writeln(format_args!(" Tables: {}", self.tables.len()));
77 }
78}
79
80impl DatabaseCliDisplay for QueryResult {
81 fn display_with_cli(&self) {
82 if self.columns.is_empty() {
83 stdout_writeln(format_args!("No data returned"));
84 return;
85 }
86
87 stdout_writeln(format_args!("{}", self.columns.join(" | ")));
88 stdout_writeln(format_args!("{}", "-".repeat(80)));
89
90 for row in &self.rows {
91 let values: Vec<String> = self
92 .columns
93 .iter()
94 .map(|col| {
95 row.get(col).map_or_else(
96 || "NULL".to_owned(),
97 |v| match v {
98 serde_json::Value::String(s) => s.clone(),
99 serde_json::Value::Null => "NULL".to_owned(),
100 serde_json::Value::Bool(_)
101 | serde_json::Value::Number(_)
102 | serde_json::Value::Array(_)
103 | serde_json::Value::Object(_) => v.to_string(),
104 },
105 )
106 })
107 .collect();
108 stdout_writeln(format_args!("{}", values.join(" | ")));
109 }
110
111 stdout_writeln(format_args!(
112 "\n{} rows returned in {}ms",
113 self.row_count, self.execution_time_ms
114 ));
115 }
116}