use crate::common::ColumnBaseInfo;
use crate::to_json::MySqlRowParse;
use base64::{engine::general_purpose, Engine};
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, TimeZone};
use rust_decimal::Decimal;
use serde_json::{json, Value as JsonValue};
use sqlx::mysql::{ MySqlRow,};
use sqlx::{ Column, Row, TypeInfo};
use crate::utlis::decode_auto;
pub fn to_json(results: Vec<MySqlRow>) -> anyhow::Result<(Vec<JsonValue>, Vec<ColumnBaseInfo>)> {
if results.is_empty() {
return Ok((vec![], vec![]));
}
let first_row = &results[0];
let MySqlRowParse { methods, columns } = determine_parsing_methods(first_row)?;
let mut data: Vec<JsonValue> = Vec::with_capacity(results.len()); for row in results {
let mut row_data = json!({});
for col in row.columns() {
let col_name = col.name();
let col_index: usize = col.ordinal();
let value = parse_value(&row, col_index, &methods);
row_data[col_name] = value;
}
data.push(row_data);
}
Ok((data, columns))
}
fn determine_parsing_methods(row: &MySqlRow) -> anyhow::Result<MySqlRowParse> {
let columns = row.columns();
let mut methods: Vec<fn(&MySqlRow, usize) -> JsonValue> = Vec::with_capacity(columns.len());
let mut new_columns = Vec::with_capacity(columns.len());
for col in columns {
let col_index: usize = col.ordinal(); let colum_name = col.name();
let field_type = col.type_info().name().to_uppercase();
let method = match field_type.as_str() {
"VARCHAR" | "CHAR" | "TEXT" => parse_text_value,
"TINYTEXT" | "MEDIUMTEXT" | "LONGTEXT" => parse_text_value,
"INTEGER" | "INT" | "SMALLINT" | "BIGINT" | "TINYINT" | "INT UNSIGNED"
| "MEDIUMINT" | "SERIAL" => parse_integer_value,
"DOUBLE" | "FLOAT" | "REAL" => parse_real_value,
"DECIMAL" | "NUMERIC" => parse_decimal,
"BOOLEAN" | "BOOL" => parse_bool_value, "DATETIME" => parse_datetime_value,
"DATE" => parse_date_value,
"TIME" => parse_time_value,
"TIMESTAMP" => parse_timestamp_with_timezone,
"YEAR" => parse_year_value,
"JSON" => parse_json_value,
"BLOB" | "TINYBLOB" => parse_blob_or_text, "MEDIUMBLOB" | "LONGBLOB" => parse_blob_value, "BINARY" => parse_blob_value, "VARBINARY" => parse_varbinary_value,
"ENUM" | "SET" => parse_text_value, "BIT" => parse_integer_value, "UNSIGNED BIG INT" => parse_integer_value,
"NCHAR" | "NVARCHAR" | "NATIONAL CHAR" | "NATIONAL VARCHAR" => parse_text_value,
"GEOMETRY" | "POINT" | "LINESTRING" | "POLYGON" | "MULTIPOINT" | "MULTILINESTRING"
| "MULTIPOLYGON" | "GEOMETRYCOLLECTION" => parse_geometry_value,
_ => {
return Err(anyhow::anyhow!("未知字段类型: {}", col.type_info().name()));
}
};
new_columns.push(ColumnBaseInfo {
name: colum_name.to_string(),
r#type: field_type,
index: col_index as u64,
});
methods.push(method);
}
Ok(MySqlRowParse {
methods,
columns: new_columns,
})
}
fn parse_value(
row: &MySqlRow,
col_index: usize,
parsing_methods: &[fn(&MySqlRow, usize) -> JsonValue],
) -> JsonValue {
let method = parsing_methods[col_index];
method(row, col_index)
}
fn parse_text_value(row: &MySqlRow, col_index: usize) -> JsonValue {
if let Ok(Some(s)) = row.try_get::<Option<String>, _>(col_index) {
JsonValue::String(s)
} else if let Ok(Some(b)) = row.try_get::<Option<Vec<u8>>, _>(col_index) {
JsonValue::String(decode_auto(&b))
} else {
JsonValue::Null
}
}
fn parse_date_value(row: &MySqlRow, col_index: usize) -> JsonValue {
if let Ok(Some(date)) = row.try_get::<Option<NaiveDate>, _>(col_index) {
JsonValue::String(date.format("%Y-%m-%d").to_string())
} else {
JsonValue::Null
}
}
fn parse_datetime_value(row: &MySqlRow, col_index: usize) -> JsonValue {
if let Ok(Some(dt)) = row.try_get::<Option<NaiveDateTime>, _>(col_index) {
JsonValue::String(dt.format("%Y-%m-%d %H:%M:%S").to_string())
} else {
JsonValue::Null
}
}
fn parse_timestamp_with_timezone(row: &MySqlRow, col_index: usize) -> JsonValue {
if let Ok(Some(ts)) = row.try_get::<Option<i64>, _>(col_index) {
let utc = DateTime::from_timestamp(ts, 0).unwrap();
let local = Local.from_utc_datetime(&utc.naive_utc());
return JsonValue::String(local.format("%Y-%m-%d %H:%M:%S %Z").to_string());
}
JsonValue::Null
}
fn parse_year_value(row: &MySqlRow, col_index: usize) -> JsonValue {
match row.try_get::<Option<i32>, _>(col_index) {
Ok(Some(year)) => JsonValue::String(year.to_string()),
Ok(None) | Err(_) => JsonValue::Null,
}
}
fn parse_time_value(row: &MySqlRow, col_index: usize) -> JsonValue {
if let Ok(Some(dt)) = row.try_get::<Option<NaiveDateTime>, _>(col_index) {
JsonValue::String(dt.format("%H:%M:%S").to_string())
} else {
JsonValue::Null
}
}
fn parse_json_value(row: &MySqlRow, col_index: usize) -> JsonValue {
match row.try_get::<Option<JsonValue>, _>(col_index) {
Ok(Some(json_value)) => json_value,
Ok(None) => JsonValue::Null,
Err(e) => {
eprintln!("解析 JSON 出错: {:?}", e);
JsonValue::Null
}
}
}
fn parse_integer_value(row: &MySqlRow, col_index: usize) -> JsonValue {
match row.try_get::<Option<i64>, _>(col_index) {
Ok(value) => {
if let Some(i) = value {
JsonValue::Number(i.into())
} else {
JsonValue::Null
}
}
Err(_) => JsonValue::Null,
}
}
fn parse_bool_value(row: &MySqlRow, col_index: usize) -> JsonValue {
if let Ok(Some(value)) = row.try_get::<Option<bool>, _>(col_index) {
JsonValue::Bool(value)
} else if let Ok(Some(value)) = row.try_get::<Option<i8>, _>(col_index) {
JsonValue::Bool(value != 0)
} else if let Ok(Some(value)) = row.try_get::<Option<String>, _>(col_index) {
match value.to_lowercase().as_str() {
"true" => JsonValue::Bool(true),
"false" => JsonValue::Bool(false),
_ => JsonValue::Null, }
} else {
JsonValue::Null
}
}
fn parse_real_value(row: &MySqlRow, col_index: usize) -> JsonValue {
match row.try_get::<Option<f64>, _>(col_index) {
Ok(value) => {
if let Some(i) = value {
JsonValue::Number(serde_json::Number::from_f64(i).unwrap())
} else {
JsonValue::Null
}
}
Err(_) => JsonValue::Null,
}
}
fn parse_blob_value(row: &MySqlRow, col_index: usize) -> JsonValue {
match row.try_get::<Option<Vec<u8>>, _>(col_index) {
Ok(value) => {
if let Some(i) = value {
JsonValue::String(general_purpose::STANDARD.encode(i))
} else {
JsonValue::Null
}
}
Err(_) => JsonValue::Null,
}
}
fn parse_blob_or_text(row: &MySqlRow, col_index: usize) -> JsonValue {
match row.try_get::<Option<Vec<u8>>, _>(col_index) {
Ok(value) => {
if let Some(i) = value {
let u = blob_is_text(&i);
if u {
return JsonValue::String(decode_auto(&i));
}
JsonValue::String(general_purpose::STANDARD.encode(i))
} else {
JsonValue::Null
}
}
Err(_) => JsonValue::Null,
}
}
fn parse_varbinary_value(row: &MySqlRow, col_index: usize) -> JsonValue {
match row.try_get::<Option<Vec<u8>>, _>(col_index) {
Ok(value) => {
if let Some(i) = value {
JsonValue::String(decode_auto(&i))
} else {
JsonValue::Null
}
}
Err(_) => JsonValue::Null,
}
}
fn parse_decimal(row: &MySqlRow, col_index: usize) -> serde_json::Value {
match row.try_get::<Option<Decimal>, _>(col_index) {
Ok(Some(d)) => serde_json::Value::String(d.to_string()),
_ => serde_json::Value::Null,
}
}
fn parse_geometry_value(_row: &MySqlRow, _col_index: usize) -> serde_json::Value {
serde_json::Value::String("Does not support parsing".to_string())
}
fn blob_is_text(data: &[u8]) -> bool {
const NON_TEXT_THRESHOLD: f32 = 0.2; const SAMPLE_SIZE: usize = 1024;
let total_len = data.len();
if total_len == 0 {
return false; }
let step = if total_len > SAMPLE_SIZE {
total_len / SAMPLE_SIZE
} else {
1 };
let mut non_printables = 0; let mut checked_count = 0;
for (_, &byte) in data.iter().step_by(step).enumerate() {
checked_count += 1;
if !byte.is_ascii_graphic() && !byte.is_ascii_whitespace() {
non_printables += 1;
}
let non_printable_ratio = (non_printables as f32) / (checked_count as f32);
if non_printable_ratio >= NON_TEXT_THRESHOLD {
return false;
}
}
true
}