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