sqlness_runner/
formatter.rs1use std::borrow::Cow;
16use std::fmt::Display;
17use std::sync::Arc;
18
19use client::{Output, OutputData, RecordBatches};
20use common_error::ext::ErrorExt;
21use datatypes::prelude::ConcreteDataType;
22use datatypes::scalars::ScalarVectorBuilder;
23use datatypes::schema::{ColumnSchema, Schema};
24use datatypes::vectors::{StringVectorBuilder, VectorRef};
25use mysql::Row as MySqlRow;
26use tokio_postgres::SimpleQueryMessage as PgRow;
27
28use crate::client::MysqlSqlResult;
29
30pub struct ErrorFormatter<E: ErrorExt>(E);
32
33impl<E: ErrorExt> From<E> for ErrorFormatter<E> {
34 fn from(error: E) -> Self {
35 ErrorFormatter(error)
36 }
37}
38
39impl<E: ErrorExt> Display for ErrorFormatter<E> {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 let status_code = self.0.status_code();
42 let root_cause = self.0.output_msg();
43 let root_cause = normalize_meta_client_error(&root_cause, status_code);
44 write!(
45 f,
46 "Error: {}({status_code}), {root_cause}",
47 status_code as u32
48 )
49 }
50}
51
52fn normalize_meta_client_error(
53 root_cause: &str,
54 status_code: common_error::status_code::StatusCode,
55) -> &str {
56 let details_prefix = format!(", code: {status_code}, tonic code: ");
57 match root_cause.rsplit_once(&details_prefix) {
58 Some((message, tonic_code)) if !tonic_code.is_empty() => message,
59 _ => root_cause,
60 }
61}
62
63pub struct OutputFormatter(Output);
65
66impl From<Output> for OutputFormatter {
67 fn from(output: Output) -> Self {
68 OutputFormatter(output)
69 }
70}
71
72impl Display for OutputFormatter {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 match &self.0.data {
75 OutputData::AffectedRows(rows) => {
76 write!(f, "Affected Rows: {rows}")
77 }
78 OutputData::RecordBatches(recordbatches) => {
79 let pretty = recordbatches.pretty_print().map_err(|e| e.to_string());
80 match pretty {
81 Ok(s) => write!(f, "{s}"),
82 Err(e) => {
83 write!(f, "Failed to pretty format {recordbatches:?}, error: {e}")
84 }
85 }
86 }
87 OutputData::Stream(_) => unreachable!(),
88 }
89 }
90}
91
92pub struct PostgresqlFormatter(Vec<PgRow>);
93
94impl From<Vec<PgRow>> for PostgresqlFormatter {
95 fn from(rows: Vec<PgRow>) -> Self {
96 PostgresqlFormatter(rows)
97 }
98}
99
100impl Display for PostgresqlFormatter {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 if self.0.is_empty() {
103 return f.write_fmt(format_args!("(Empty response)"));
104 }
105
106 if let PgRow::CommandComplete(affected_rows) = &self.0[0] {
107 return write!(
108 f,
109 "{}",
110 OutputFormatter(Output::new_with_affected_rows(*affected_rows as usize))
111 );
112 };
113
114 let Some(recordbatches) = build_recordbatches_from_postgres_rows(&self.0) else {
115 return Ok(());
116 };
117 write!(
118 f,
119 "{}",
120 OutputFormatter(Output::new_with_record_batches(recordbatches))
121 )
122 }
123}
124
125fn build_recordbatches_from_postgres_rows(rows: &[PgRow]) -> Option<RecordBatches> {
126 let schema = match &rows[0] {
128 PgRow::RowDescription(desc) => Arc::new(Schema::new(
129 desc.iter()
130 .map(|column| {
131 ColumnSchema::new(column.name(), ConcreteDataType::string_datatype(), true)
132 })
133 .collect(),
134 )),
135 _ => unreachable!(),
136 };
137 if schema.num_columns() == 0 {
138 return None;
139 }
140
141 let mut columns: Vec<StringVectorBuilder> = (0..schema.num_columns())
143 .map(|_| StringVectorBuilder::with_capacity(schema.num_columns()))
144 .collect();
145 for row in rows.iter().skip(1) {
146 if let PgRow::Row(row) = row {
147 for (i, column) in columns.iter_mut().enumerate().take(schema.num_columns()) {
148 column.push(row.get(i));
149 }
150 }
151 }
152 let columns: Vec<VectorRef> = columns
153 .into_iter()
154 .map(|mut col| Arc::new(col.finish()) as VectorRef)
155 .collect();
156
157 let recordbatches = RecordBatches::try_from_columns(schema, columns)
159 .expect("Failed to construct recordbatches from columns. Please check the schema.");
160 Some(recordbatches)
161}
162
163pub struct MysqlFormatter(MysqlSqlResult);
165
166impl From<MysqlSqlResult> for MysqlFormatter {
167 fn from(result: MysqlSqlResult) -> Self {
168 MysqlFormatter(result)
169 }
170}
171
172impl Display for MysqlFormatter {
173 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174 match &self.0 {
175 MysqlSqlResult::AffectedRows(rows) => {
176 write!(f, "affected_rows: {rows}")
177 }
178 MysqlSqlResult::Rows(rows) => {
179 if rows.is_empty() {
180 return f.write_fmt(format_args!("(Empty response)"));
181 }
182
183 let recordbatches = build_recordbatches_from_mysql_rows(rows);
184 write!(
185 f,
186 "{}",
187 OutputFormatter(Output::new_with_record_batches(recordbatches))
188 )
189 }
190 }
191 }
192}
193
194pub fn build_recordbatches_from_mysql_rows(rows: &[MySqlRow]) -> RecordBatches {
195 let head_column = &rows[0];
197 let head_binding = head_column.columns();
198 let names = head_binding
199 .iter()
200 .map(|column| column.name_str())
201 .collect::<Vec<Cow<str>>>();
202 let schema = Arc::new(Schema::new(
203 names
204 .iter()
205 .map(|name| {
206 ColumnSchema::new(name.to_string(), ConcreteDataType::string_datatype(), true)
207 })
208 .collect(),
209 ));
210
211 let mut columns: Vec<StringVectorBuilder> = (0..schema.num_columns())
213 .map(|_| StringVectorBuilder::with_capacity(schema.num_columns()))
214 .collect();
215 for row in rows.iter() {
216 for (i, name) in names.iter().enumerate() {
217 let value: Option<String> = row.get::<Option<String>, &str>(name).flatten();
219 columns[i].push(value.as_deref());
220 }
221 }
222 let columns: Vec<VectorRef> = columns
223 .into_iter()
224 .map(|mut col| Arc::new(col.finish()) as VectorRef)
225 .collect();
226
227 RecordBatches::try_from_columns(schema, columns)
229 .expect("Failed to construct recordbatches from columns. Please check the schema.")
230}
231
232#[cfg(test)]
233mod tests {
234 use common_error::ext::PlainError;
235 use common_error::status_code::StatusCode;
236
237 use super::*;
238
239 #[test]
240 fn test_normalize_meta_client_error() {
241 let error = PlainError::new(
242 "Invalid options, code: InvalidArguments, tonic code: Client specified an invalid argument"
243 .to_string(),
244 StatusCode::InvalidArguments,
245 );
246
247 assert_eq!(
248 "Error: 1004(InvalidArguments), Invalid options",
249 ErrorFormatter::from(error).to_string()
250 );
251 }
252
253 #[test]
254 fn test_preserve_regular_error() {
255 let error = PlainError::new(
256 "Invalid options without transport details".to_string(),
257 StatusCode::InvalidArguments,
258 );
259
260 assert_eq!(
261 "Error: 1004(InvalidArguments), Invalid options without transport details",
262 ErrorFormatter::from(error).to_string()
263 );
264 }
265
266 #[test]
267 fn test_preserve_error_with_mismatched_code() {
268 let error = PlainError::new(
269 "Invalid options, code: Unsupported, tonic code: Operation is not supported"
270 .to_string(),
271 StatusCode::InvalidArguments,
272 );
273
274 assert_eq!(
275 "Error: 1004(InvalidArguments), Invalid options, code: Unsupported, tonic code: Operation is not supported",
276 ErrorFormatter::from(error).to_string()
277 );
278 }
279}