Skip to main content

systemprompt_database/services/
display.rs

1//! CLI display traits for printing query results, table descriptors, and
2//! database info to stdout.
3
4use std::io::Write;
5
6use crate::models::{ColumnInfo, DatabaseInfo, QueryResult, TableInfo};
7
8pub trait DatabaseCliDisplay {
9    fn display_with_cli(&self);
10}
11
12fn stdout_writeln(args: std::fmt::Arguments<'_>) {
13    let mut stdout = std::io::stdout();
14    writeln!(stdout, "{args}").ok();
15}
16
17impl DatabaseCliDisplay for Vec<TableInfo> {
18    fn display_with_cli(&self) {
19        if self.is_empty() {
20            stdout_writeln(format_args!("No tables found"));
21        } else {
22            stdout_writeln(format_args!("Tables:"));
23            for table in self {
24                stdout_writeln(format_args!("  {} (rows: {})", table.name, table.row_count));
25            }
26        }
27    }
28}
29
30impl DatabaseCliDisplay for (Vec<ColumnInfo>, i64) {
31    fn display_with_cli(&self) {
32        let (columns, _) = self;
33        stdout_writeln(format_args!("Columns:"));
34        for col in columns {
35            let default_display = col
36                .default
37                .as_deref()
38                .map_or_else(String::new, |d| format!("DEFAULT {d}"));
39
40            stdout_writeln(format_args!(
41                "  {} {} {} {} {}",
42                col.name,
43                col.data_type,
44                if col.nullable { "NULL" } else { "NOT NULL" },
45                if col.primary_key { "PK" } else { "" },
46                default_display
47            ));
48        }
49    }
50}
51
52impl DatabaseCliDisplay for DatabaseInfo {
53    fn display_with_cli(&self) {
54        stdout_writeln(format_args!("Database Info:"));
55        stdout_writeln(format_args!("  Path: {}", self.path));
56        stdout_writeln(format_args!("  Version: {}", self.version));
57        stdout_writeln(format_args!("  Tables: {}", self.tables.len()));
58    }
59}
60
61impl DatabaseCliDisplay for QueryResult {
62    fn display_with_cli(&self) {
63        if self.columns.is_empty() {
64            stdout_writeln(format_args!("No data returned"));
65            return;
66        }
67
68        stdout_writeln(format_args!("{}", self.columns.join(" | ")));
69        stdout_writeln(format_args!("{}", "-".repeat(80)));
70
71        for row in &self.rows {
72            let values: Vec<String> = self
73                .columns
74                .iter()
75                .map(|col| {
76                    row.get(col).map_or_else(
77                        || "NULL".to_string(),
78                        |v| match v {
79                            serde_json::Value::String(s) => s.clone(),
80                            serde_json::Value::Null => "NULL".to_string(),
81                            serde_json::Value::Bool(_)
82                            | serde_json::Value::Number(_)
83                            | serde_json::Value::Array(_)
84                            | serde_json::Value::Object(_) => v.to_string(),
85                        },
86                    )
87                })
88                .collect();
89            stdout_writeln(format_args!("{}", values.join(" | ")));
90        }
91
92        stdout_writeln(format_args!(
93            "\n{} rows returned in {}ms",
94            self.row_count, self.execution_time_ms
95        ));
96    }
97}