servers/http/result/
table_result.rs1use std::cmp::max;
16use std::fmt::{Display, Write};
17
18use axum::http::{header, HeaderValue};
19use axum::response::{IntoResponse, Response};
20use common_error::status_code::StatusCode;
21use common_query::Output;
22use itertools::Itertools;
23use mime_guess::mime;
24use serde::{Deserialize, Serialize};
25
26use crate::http::header::{GREPTIME_DB_HEADER_EXECUTION_TIME, GREPTIME_DB_HEADER_FORMAT};
27use crate::http::result::error_result::ErrorResponse;
28use crate::http::{handler, process_with_limit, GreptimeQueryOutput, HttpResponse, ResponseFormat};
29
30#[derive(Serialize, Deserialize, Debug)]
31pub struct TableResponse {
32 output: Vec<GreptimeQueryOutput>,
33 execution_time_ms: u64,
34}
35
36impl TableResponse {
37 pub async fn from_output(outputs: Vec<crate::error::Result<Output>>) -> HttpResponse {
38 match handler::from_output(outputs).await {
39 Err(err) => HttpResponse::Error(err),
40 Ok((output, _)) => {
41 if output.len() > 1 {
42 HttpResponse::Error(ErrorResponse::from_error_message(
43 StatusCode::InvalidArguments,
44 "cannot output multi-statements result in table format".to_string(),
45 ))
46 } else {
47 HttpResponse::Table(TableResponse {
48 output,
49 execution_time_ms: 0,
50 })
51 }
52 }
53 }
54 }
55
56 pub fn output(&self) -> &[GreptimeQueryOutput] {
57 &self.output
58 }
59
60 pub fn with_execution_time(mut self, execution_time: u64) -> Self {
61 self.execution_time_ms = execution_time;
62 self
63 }
64
65 pub fn execution_time_ms(&self) -> u64 {
66 self.execution_time_ms
67 }
68
69 pub fn with_limit(mut self, limit: usize) -> Self {
70 self.output = process_with_limit(self.output, limit);
71 self
72 }
73}
74
75impl Display for TableResponse {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 let payload = match self.output.first() {
78 None => String::default(),
79 Some(GreptimeQueryOutput::AffectedRows(n)) => {
80 format!("{n}\n")
81 }
82 Some(GreptimeQueryOutput::Records(records)) => {
83 let mut max_width = vec![0; records.num_cols()];
84 let mut result = String::new();
85 for (i, column) in records.schema.column_schemas.iter().enumerate() {
87 max_width[i] = max(max_width[i], column.name.len());
88 }
89 for row in &records.rows {
90 for (i, v) in row.iter().enumerate() {
91 let s = v.to_string();
92 max_width[i] = max(max_width[i], s.len());
93 }
94 }
95
96 let head: String = records
98 .schema
99 .column_schemas
100 .iter()
101 .enumerate()
102 .map(|(i, column)| format!("─{:─<1$}─", column.name, max_width[i]))
103 .join("┬");
104 writeln!(result, "┌{}┐", head).unwrap();
105
106 for row in &records.rows {
108 let row = row
109 .iter()
110 .enumerate()
111 .map(|(i, v)| {
112 let s = v.to_string();
113 format!(" {:1$} ", s, max_width[i])
114 })
115 .join("│");
116 writeln!(result, "│{row}│").unwrap();
117 }
118
119 let footer: String = max_width.iter().map(|v| "─".repeat(*v + 2)).join("┴");
121 writeln!(result, "└{}┘", footer).unwrap();
122 result
123 }
124 };
125 write!(f, "{}", payload)
126 }
127}
128
129impl IntoResponse for TableResponse {
130 fn into_response(self) -> Response {
131 debug_assert!(
132 self.output.len() <= 1,
133 "self.output has extra elements: {}",
134 self.output.len()
135 );
136
137 let execution_time = self.execution_time_ms;
138
139 let mut resp = (
140 [(
141 header::CONTENT_TYPE,
142 HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
143 )],
144 self.to_string(),
145 )
146 .into_response();
147 resp.headers_mut().insert(
148 &GREPTIME_DB_HEADER_FORMAT,
149 HeaderValue::from_static(ResponseFormat::Table.as_str()),
150 );
151 resp.headers_mut().insert(
152 &GREPTIME_DB_HEADER_EXECUTION_TIME,
153 HeaderValue::from(execution_time),
154 );
155 resp
156 }
157}
158#[cfg(test)]
159mod test {
160
161 use super::TableResponse;
162
163 #[tokio::test]
164 async fn test_table_format() {
165 let data = r#"{"output":[{"records":{"schema":{"column_schemas":[{"name":"host","data_type":"String"},{"name":"ts","data_type":"TimestampMillisecond"},{"name":"cpu","data_type":"Float64"},{"name":"memory","data_type":"Float64"}]},"rows":[["127.0.0.1",1702433141000,0.5,0.2],["127.0.0.1",1702433146000,0.3,0.2],["127.0.0.1",1702433151000,0.4,0.3],["127.0.0.2",1702433141000,0.3,0.1],["127.0.0.2",1702433146000,0.2,0.4],["127.0.0.2",1702433151000,0.2,0.4]]}}],"execution_time_ms":13}"#;
166 let table_response: TableResponse = serde_json::from_str(data).unwrap();
167 let payload = table_response.to_string();
168 let expected_payload = r#"┌─host────────┬─ts────────────┬─cpu─┬─memory─┐
169│ "127.0.0.1" │ 1702433141000 │ 0.5 │ 0.2 │
170│ "127.0.0.1" │ 1702433146000 │ 0.3 │ 0.2 │
171│ "127.0.0.1" │ 1702433151000 │ 0.4 │ 0.3 │
172│ "127.0.0.2" │ 1702433141000 │ 0.3 │ 0.1 │
173│ "127.0.0.2" │ 1702433146000 │ 0.2 │ 0.4 │
174│ "127.0.0.2" │ 1702433151000 │ 0.2 │ 0.4 │
175└─────────────┴───────────────┴─────┴────────┘
176"#;
177 assert_eq!(payload, expected_payload);
178 }
179}