feat(query): show database server messages in query results

This commit is contained in:
Eddy Lei authored and GitHub committed 2026-08-07 15:18:32 +08:00
1 parent 4c7ddd266b
commit a5e46fd659
45 files changed
+1401 -51

No files matched your search

+80 -7
View File
@@ -4,7 +4,7 @@ use dbx_core::{
models::connection::{ConnectionConfig, DatabaseType},
production_safety::{is_production_database, targets_production_database},
sql_risk::{classify_sql_risk_for_database, SqlRisk},
types::{ColumnInfo, QueryResult, TableInfo},
types::{ColumnInfo, QueryMessage, QueryResult, TableInfo},
};
use dbx_mcp::{
backend::DocsSnapshotOptions,
@@ -742,24 +742,39 @@ fn format_query(connection: &str, result: &QueryResult, format: OutputFormat) ->
.collect();
let row_count = if result.columns.is_empty() { result.affected_rows } else { result.rows.len() as u64 };
match format {
OutputFormat::Json => json_string(
&json!({ "connection": connection, "columns": result.columns, "rows": rows, "row_count": row_count }),
),
OutputFormat::Json => {
let mut output =
json!({ "connection": connection, "columns": result.columns, "rows": rows, "row_count": row_count });
if !result.messages.is_empty() {
output["messages"] = json!(result.messages);
}
json_string(&output)
}
OutputFormat::Csv => Ok(csv_table(&result.columns.iter().map(String::as_str).collect::<Vec<_>>(), &rows)),
OutputFormat::Table if result.columns.is_empty() => {
Ok(format!("Query executed. {row_count} row(s) affected.\n"))
Ok(format!("Query executed. {row_count} row(s) affected.\n{}", format_query_messages(&result.messages)))
}
OutputFormat::Table => Ok(format!(
"{}\n\n{row_count} row(s)\n",
"{}\n\n{row_count} row(s)\n{}",
markdown_table(
&result.columns.iter().map(String::as_str).collect::<Vec<_>>(),
&rows,
&result.columns.iter().map(String::as_str).collect::<Vec<_>>()
)
),
format_query_messages(&result.messages)
)),
}
}
fn format_query_messages(messages: &[QueryMessage]) -> String {
let mut output = String::new();
for message in messages {
output.push_str(&message.format_line());
output.push('\n');
}
output
}
fn format_capabilities(format: OutputFormat) -> Result<String, CliError> {
match format {
OutputFormat::Json => json_string(
@@ -997,6 +1012,7 @@ mod tests {
session_id: None,
has_more: false,
elasticsearch_raw_body: None,
messages: Vec::new(),
})
}
@@ -1047,6 +1063,63 @@ mod tests {
assert_eq!(risk, SqlRisk::Ddl);
}
#[test]
fn format_query_renders_server_messages_in_table_output() {
let mut result = QueryResult {
columns: Vec::new(),
column_types: Vec::new(),
column_sortables: Vec::new(),
spatial_columns: vec![],
spatial_values: vec![],
rows: Vec::new(),
affected_rows: 1,
execution_time_ms: 0,
truncated: false,
session_id: None,
has_more: false,
elasticsearch_raw_body: None,
messages: vec![
QueryMessage {
severity: "notice".to_string(),
message: "hello world".to_string(),
code: Some("00000".to_string()),
detail: None,
hint: Some("use a table".to_string()),
},
QueryMessage {
severity: "WARNING".to_string(),
message: "careful".to_string(),
code: None,
detail: None,
hint: None,
},
],
};
let output = format_query("local", &result, OutputFormat::Table).unwrap();
assert_eq!(
output,
"Query executed. 1 row(s) affected.\nNOTICE: hello world (code: 00000, hint: use a table)\nWARNING: careful\n"
);
let output = format_query("local", &result, OutputFormat::Json).unwrap();
let value: Value = serde_json::from_str(&output).unwrap();
assert_eq!(
value["messages"],
json!([
{ "severity": "notice", "message": "hello world", "code": "00000", "hint": "use a table" },
{ "severity": "WARNING", "message": "careful" },
])
);
result.messages = Vec::new();
let output = format_query("local", &result, OutputFormat::Table).unwrap();
assert_eq!(output, "Query executed. 1 row(s) affected.\n");
let output = format_query("local", &result, OutputFormat::Json).unwrap();
let value: Value = serde_json::from_str(&output).unwrap();
assert!(value.get("messages").is_none());
}
#[tokio::test]
async fn routes_legacy_mongo_insert_through_shared_mongo_backend() {
let flags = parse_flags(&args(&[