use crate::column_info::ColumnBaseInfo;
use crate::to_json::MySqlRowParse;
use base64::{engine::general_purpose, Engine};
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, Utc};
use rust_decimal::Decimal;
use serde_json::Value as JsonValue;
use sqlx::mysql::MySqlRow;
use sqlx::{Column, Row, TypeInfo};
use crate::decode::decode_auto;
use super::f64_to_json_safe;
crate::impl_to_json!(MySqlRow, MySqlRowParse);
fn determine_parsing_methods(row: &MySqlRow) -> anyhow::Result<MySqlRowParse> {
let customizer = super::get_customizer();
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: fn(&MySqlRow, usize) -> JsonValue = customizer
.customize_mysql(&field_type)
.map(Ok)
.unwrap_or_else(|| default_mysql_method(&field_type, col))?;
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,
})
}
#[inline]
fn default_mysql_method(
field_type: &str,
col: &sqlx::mysql::MySqlColumn,
) -> anyhow::Result<fn(&MySqlRow, usize) -> JsonValue> {
let method = match field_type {
"VARCHAR" | "CHAR" | "TEXT" | "TINYTEXT" | "MEDIUMTEXT" | "LONGTEXT" => parse_text_value,
"INTEGER" | "INT" | "SMALLINT" | "BIGINT" | "TINYINT" | "MEDIUMINT" | "SERIAL"
| "INT UNSIGNED" | "BIGINT UNSIGNED" | "SMALLINT UNSIGNED" | "TINYINT UNSIGNED"
| "MEDIUMINT UNSIGNED" | "UNSIGNED BIG INT" | "BIT" => parse_integer_value,
"DOUBLE" | "FLOAT" | "REAL" | "DOUBLE UNSIGNED" | "FLOAT UNSIGNED" => parse_real_value,
"DECIMAL" | "NUMERIC" | "DECIMAL UNSIGNED" | "NUMERIC UNSIGNED" => 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_binary_value,
"VARBINARY" => parse_varbinary_value,
"ENUM" => parse_text_value,
"SET" => parse_set_value,
"NCHAR" | "NVARCHAR" | "NATIONAL CHAR" | "NATIONAL VARCHAR" => parse_text_value,
"GEOMETRY" | "POINT" | "LINESTRING" | "POLYGON" | "MULTIPOINT" | "MULTILINESTRING"
| "MULTIPOLYGON" | "GEOMETRYCOLLECTION" => parse_geometry_value,
"VECTOR" => parse_vector_value,
_ => {
return Err(anyhow::anyhow!("Unknown field type: {}", col.type_info().name()));
}
};
Ok(method)
}
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(dt)) = row.try_get::<Option<DateTime<Utc>>, _>(col_index) {
return JsonValue::String(dt.with_timezone(&Local).format("%Y-%m-%d %H:%M:%S %Z").to_string());
}
if let Ok(Some(dt)) = row.try_get::<Option<DateTime<Local>>, _>(col_index) {
return JsonValue::String(dt.format("%Y-%m-%d %H:%M:%S %Z").to_string());
}
if let Ok(Some(dt)) = row.try_get::<Option<NaiveDateTime>, _>(col_index) {
return JsonValue::String(dt.format("%Y-%m-%d %H:%M:%S").to_string());
}
JsonValue::Null
}
fn parse_year_value(row: &MySqlRow, col_index: usize) -> JsonValue {
if let Ok(Some(year)) = row.try_get::<Option<i32>, _>(col_index) {
return JsonValue::String(year.to_string());
}
if let Ok(Some(year)) = row.try_get::<Option<i16>, _>(col_index) {
return JsonValue::String(year.to_string());
}
if let Ok(Some(year)) = row.try_get::<Option<u16>, _>(col_index) {
return JsonValue::String(year.to_string());
}
JsonValue::Null
}
fn parse_time_value(row: &MySqlRow, col_index: usize) -> JsonValue {
if let Ok(Some(t)) = row.try_get::<Option<NaiveTime>, _>(col_index) {
return JsonValue::String(t.format("%H:%M:%S").to_string());
}
if let Ok(Some(s)) = row.try_get::<Option<String>, _>(col_index) {
return JsonValue::String(s);
}
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,
_ => JsonValue::Null,
}
}
fn parse_integer_value(row: &MySqlRow, col_index: usize) -> JsonValue {
if let Ok(Some(i)) = row.try_get::<Option<i64>, _>(col_index) {
return JsonValue::Number(i.into());
}
if let Ok(Some(i)) = row.try_get::<Option<i32>, _>(col_index) {
return JsonValue::Number((i as i64).into());
}
if let Ok(Some(i)) = row.try_get::<Option<i16>, _>(col_index) {
return JsonValue::Number((i as i64).into());
}
if let Ok(Some(i)) = row.try_get::<Option<i8>, _>(col_index) {
return JsonValue::Number((i as i64).into());
}
if let Ok(Some(u)) = row.try_get::<Option<u64>, _>(col_index) {
if let Some(n) = serde_json::Number::from_f64(u as f64) {
return JsonValue::Number(n);
}
}
if let Ok(Some(u)) = row.try_get::<Option<u32>, _>(col_index) {
return JsonValue::Number((u as i64).into());
}
if let Ok(Some(u)) = row.try_get::<Option<u16>, _>(col_index) {
return JsonValue::Number((u as i64).into());
}
if let Ok(Some(u)) = row.try_get::<Option<u8>, _>(col_index) {
return JsonValue::Number((u as i64).into());
}
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(Some(f)) => f64_to_json_safe(f),
_ => JsonValue::Null,
}
}
fn parse_blob_value(row: &MySqlRow, col_index: usize) -> JsonValue {
match row.try_get::<Option<Vec<u8>>, _>(col_index) {
Ok(Some(bytes)) => JsonValue::String(general_purpose::STANDARD.encode(bytes)),
_ => JsonValue::Null,
}
}
fn parse_binary_value(row: &MySqlRow, col_index: usize) -> JsonValue {
match row.try_get::<Option<Vec<u8>>, _>(col_index) {
Ok(Some(bytes)) => {
if bytes.len() == 16 {
if let Ok(uuid) = uuid::Uuid::from_slice(&bytes) {
return JsonValue::String(uuid.to_string());
}
}
JsonValue::String(general_purpose::STANDARD.encode(bytes))
}
_ => JsonValue::Null,
}
}
fn parse_set_value(row: &MySqlRow, col_index: usize) -> JsonValue {
match row.try_get::<Option<String>, _>(col_index) {
Ok(Some(s)) if !s.is_empty() => JsonValue::Array(
s.split(',')
.map(|v| JsonValue::String(v.trim().to_string()))
.collect(),
),
Ok(Some(_)) => JsonValue::Array(vec![]),
_ => 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 = super::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 parse_vector_value(row: &MySqlRow, col_index: usize) -> JsonValue {
if let Ok(Some(s)) = row.try_get::<Option<String>, _>(col_index) {
return super::parse_vector_string(&s);
}
if let Ok(Some(bytes)) = row.try_get::<Option<Vec<u8>>, _>(col_index) {
return super::parse_vector_bytes(&bytes);
}
JsonValue::Null
}