fix: render binary values as hex (#465)
This commit is contained in:
9 files changed
+128
-29
No files matched your search
@@ -45,6 +45,20 @@ pub fn safe_u64_to_json(v: u64) -> serde_json::Value {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn hex_encode(bytes: &[u8]) -> String {
|
||||
const HEX: &[u8; 16] = b"0123456789abcdef";
|
||||
let mut out = String::with_capacity(bytes.len() * 2);
|
||||
for &byte in bytes {
|
||||
out.push(HEX[(byte >> 4) as usize] as char);
|
||||
out.push(HEX[(byte & 0x0f) as usize] as char);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub(crate) fn binary_value_to_json(bytes: &[u8]) -> serde_json::Value {
|
||||
serde_json::Value::String(format!("0x{}", hex_encode(bytes)))
|
||||
}
|
||||
|
||||
pub fn tcp_probe_timeout() -> Duration {
|
||||
Duration::from_secs(TCP_PROBE_TIMEOUT_SECS)
|
||||
}
|
||||
@@ -89,3 +103,13 @@ pub async fn probe_tcp_endpoint(label: &str, host: &str, port: u16) -> Result<()
|
||||
.map(|_| ())
|
||||
.map_err(|e| format!("{label} TCP connection failed: {e}"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn binary_values_are_displayed_as_prefixed_hex() {
|
||||
assert_eq!(binary_value_to_json(&[0x00, 0x01, 0xab, 0xff]), serde_json::json!("0x0001abff"));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
|
||||
use futures::StreamExt;
|
||||
use mysql_async::consts::ColumnType;
|
||||
use mysql_async::consts::{ColumnFlags, ColumnType};
|
||||
use mysql_async::prelude::*;
|
||||
use rust_decimal::Decimal;
|
||||
use std::borrow::Cow;
|
||||
@@ -84,6 +84,30 @@ fn is_lossless_integer_column(column: &mysql_async::Column) -> bool {
|
||||
matches!(column.column_type(), ColumnType::MYSQL_TYPE_LONGLONG | ColumnType::MYSQL_TYPE_NEWDECIMAL)
|
||||
}
|
||||
|
||||
fn is_binary_column(column: &mysql_async::Column) -> bool {
|
||||
let binary_flag = column.flags().contains(ColumnFlags::BINARY_FLAG);
|
||||
let binary_charset = column.character_set() == 63;
|
||||
matches!(column.column_type(), ColumnType::MYSQL_TYPE_GEOMETRY)
|
||||
|| ((binary_flag || binary_charset)
|
||||
&& matches!(
|
||||
column.column_type(),
|
||||
ColumnType::MYSQL_TYPE_BLOB
|
||||
| ColumnType::MYSQL_TYPE_LONG_BLOB
|
||||
| ColumnType::MYSQL_TYPE_MEDIUM_BLOB
|
||||
| ColumnType::MYSQL_TYPE_TINY_BLOB
|
||||
| ColumnType::MYSQL_TYPE_STRING
|
||||
| ColumnType::MYSQL_TYPE_VAR_STRING
|
||||
| ColumnType::MYSQL_TYPE_VARCHAR
|
||||
))
|
||||
}
|
||||
|
||||
fn mysql_bytes_to_json(bytes: Vec<u8>, column: &mysql_async::Column) -> serde_json::Value {
|
||||
if is_binary_column(column) {
|
||||
return super::binary_value_to_json(&bytes);
|
||||
}
|
||||
serde_json::Value::String(String::from_utf8_lossy(&bytes).to_string())
|
||||
}
|
||||
|
||||
fn mysql_value_to_json(row: &mysql_async::Row, idx: usize) -> serde_json::Value {
|
||||
let Some(column) = row.columns_ref().get(idx) else {
|
||||
return serde_json::Value::Null;
|
||||
@@ -96,6 +120,12 @@ fn mysql_value_to_json(row: &mysql_async::Row, idx: usize) -> serde_json::Value
|
||||
return serde_json::Value::Null;
|
||||
}
|
||||
|
||||
if is_binary_column(column) {
|
||||
return row_get::<Vec<u8>, _>(row, idx)
|
||||
.map(|bytes| super::binary_value_to_json(&bytes))
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
|
||||
match column.column_type() {
|
||||
ColumnType::MYSQL_TYPE_JSON => {
|
||||
if let Some(v) = row_get::<String, _>(row, idx) {
|
||||
@@ -114,8 +144,7 @@ fn mysql_value_to_json(row: &mysql_async::Row, idx: usize) -> serde_json::Value
|
||||
.or_else(|| row_get::<i64, _>(row, idx).map(|v| serde_json::Value::String(v.to_string())))
|
||||
.or_else(|| row_get::<u64, _>(row, idx).map(|v| serde_json::Value::String(v.to_string())))
|
||||
.or_else(|| {
|
||||
row_get::<Vec<u8>, _>(row, idx)
|
||||
.map(|b| serde_json::Value::String(String::from_utf8_lossy(&b).to_string()))
|
||||
row_get::<Vec<u8>, _>(row, idx).map(|bytes| mysql_bytes_to_json(bytes, column))
|
||||
})
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
@@ -133,6 +162,15 @@ fn mysql_value_to_json(row: &mysql_async::Row, idx: usize) -> serde_json::Value
|
||||
})
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
ColumnType::MYSQL_TYPE_BLOB
|
||||
| ColumnType::MYSQL_TYPE_LONG_BLOB
|
||||
| ColumnType::MYSQL_TYPE_MEDIUM_BLOB
|
||||
| ColumnType::MYSQL_TYPE_TINY_BLOB
|
||||
| ColumnType::MYSQL_TYPE_GEOMETRY => {
|
||||
return row_get::<Vec<u8>, _>(row, idx)
|
||||
.map(|bytes| mysql_bytes_to_json(bytes, column))
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
ColumnType::MYSQL_TYPE_TIMESTAMP
|
||||
| ColumnType::MYSQL_TYPE_TIMESTAMP2
|
||||
| ColumnType::MYSQL_TYPE_DATETIME
|
||||
@@ -167,7 +205,7 @@ fn mysql_value_to_json(row: &mysql_async::Row, idx: usize) -> serde_json::Value
|
||||
})
|
||||
.or_else(|| row_get::<bool, _>(row, idx).map(serde_json::Value::Bool))
|
||||
.or_else(|| {
|
||||
row_get::<Vec<u8>, _>(row, idx).map(|b| serde_json::Value::String(String::from_utf8_lossy(&b).to_string()))
|
||||
row_get::<Vec<u8>, _>(row, idx).map(|bytes| mysql_bytes_to_json(bytes, column))
|
||||
})
|
||||
.unwrap_or(serde_json::Value::Null)
|
||||
}
|
||||
|
||||
@@ -124,6 +124,13 @@ fn pg_array_to_json_value(row: &Row, idx: usize) -> Option<serde_json::Value> {
|
||||
fn pg_value_to_json(row: &Row, idx: usize, type_name: &str) -> serde_json::Value {
|
||||
let upper = type_name.to_uppercase();
|
||||
|
||||
if upper == "BYTEA" {
|
||||
return row
|
||||
.try_get::<_, Vec<u8>>(idx)
|
||||
.map(|bytes| super::binary_value_to_json(&bytes))
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
|
||||
if upper == "JSON" || upper == "JSONB" {
|
||||
if let Ok(v) = row.try_get::<_, serde_json::Value>(idx) {
|
||||
return serde_json::Value::String(v.to_string());
|
||||
@@ -195,13 +202,7 @@ fn pg_value_to_json(row: &Row, idx: usize, type_name: &str) -> serde_json::Value
|
||||
.or_else(|_| row.try_get::<_, uuid::Uuid>(idx).map(|v| serde_json::Value::String(v.to_string())))
|
||||
.or_else(|e| pg_temporal_to_json_value(row, idx).ok_or(e))
|
||||
.or_else(|_| {
|
||||
row.try_get::<_, Vec<u8>>(idx).map(|bytes| match std::str::from_utf8(&bytes) {
|
||||
Ok(s) => serde_json::Value::String(s.to_string()),
|
||||
Err(_) => {
|
||||
let hex: String = bytes.iter().map(|b| format!("{:02x}", b)).collect();
|
||||
serde_json::Value::String(hex)
|
||||
}
|
||||
})
|
||||
row.try_get::<_, Vec<u8>>(idx).map(|bytes| super::binary_value_to_json(&bytes))
|
||||
})
|
||||
.unwrap_or(serde_json::Value::Null)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use base64::prelude::{Engine as _, BASE64_STANDARD};
|
||||
use rusqlite::types::ValueRef;
|
||||
use rusqlite::{Connection, OpenFlags};
|
||||
use std::path::Path;
|
||||
@@ -542,6 +541,6 @@ fn value_ref_to_json(value: ValueRef<'_>) -> serde_json::Value {
|
||||
serde_json::Number::from_f64(v).map(serde_json::Value::Number).unwrap_or(serde_json::Value::Null)
|
||||
}
|
||||
ValueRef::Text(v) => serde_json::Value::String(String::from_utf8_lossy(v).to_string()),
|
||||
ValueRef::Blob(v) => serde_json::Value::String(BASE64_STANDARD.encode(v)),
|
||||
ValueRef::Blob(v) => super::binary_value_to_json(v),
|
||||
}
|
||||
}
|
||||
@@ -241,21 +241,11 @@ fn sqlserver_cell_to_json(cell: &ColumnData<'static>) -> serde_json::Value {
|
||||
return serde_json::Value::String(v.to_string());
|
||||
}
|
||||
if let Ok(Some(v)) = <Vec<u8> as tiberius::FromSqlOwned>::from_sql_owned(cell.clone()) {
|
||||
return serde_json::Value::String(format!("0x{}", hex_encode(&v)));
|
||||
return super::binary_value_to_json(&v);
|
||||
}
|
||||
serde_json::Value::Null
|
||||
}
|
||||
|
||||
fn hex_encode(bytes: &[u8]) -> String {
|
||||
const HEX: &[u8; 16] = b"0123456789abcdef";
|
||||
let mut out = String::with_capacity(bytes.len() * 2);
|
||||
for byte in bytes {
|
||||
out.push(HEX[(byte >> 4) as usize] as char);
|
||||
out.push(HEX[(byte & 0x0f) as usize] as char);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub async fn list_databases(client: &mut SqlServerClient) -> Result<Vec<DatabaseInfo>, String> {
|
||||
let stream = client
|
||||
.query(
|
||||
|
||||
Reference in new issue
Block a user