use std::borrow::Cow;
use std::error::Error;
use std::sync::Arc;
use std::any::Any;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
use sqlx::encode::IsNull;
use sqlx::mysql::{MySql, MySqlTypeInfo};
use sqlx::{Encode, Type, TypeInfo};
use sqlx::types::{Decimal, Uuid};
use serde_json::Value;
use crate::common::conversion::{unwrap_option, ValueConvert};
#[derive(Default, Debug, Clone, PartialEq)]
pub enum DataKind {
#[default]
Null,
Bool(bool),
TinyInt(i8), SmallInt(i16), Int(i32), BigInt(i64), UnsignedTinyInt(u8), UnsignedSmallInt(u16), UnsignedInt(u32), UnsignedBigInt(u64), Float(f32), Double(f64),
Decimal(Decimal),
Text(String),
Blob(Arc<[u8]>),
Date(NaiveDate), Time(NaiveTime), DateTime(NaiveDateTime), Timestamp(DateTime<Utc>),
Json(Arc<Value>),
Uuid(Uuid),
IpAddr(IpAddr), Ipv4Addr(Ipv4Addr), Ipv6Addr(Ipv6Addr), }
impl Encode<'_, MySql> for DataKind {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, Box<dyn Error + Send + Sync>> {
match self {
DataKind::Null => Ok(IsNull::Yes),
DataKind::Bool(b) => <bool as Encode<'_, MySql>>::encode(*b, buf),
DataKind::TinyInt(i) => <i8 as Encode<'_, MySql>>::encode(*i, buf),
DataKind::SmallInt(i) => <i16 as Encode<'_, MySql>>::encode(*i, buf),
DataKind::Int(i) => <i32 as Encode<'_, MySql>>::encode(*i, buf),
DataKind::BigInt(i) => <i64 as Encode<'_, MySql>>::encode(*i, buf),
DataKind::UnsignedTinyInt(i) => <u8 as Encode<'_, MySql>>::encode(*i, buf),
DataKind::UnsignedSmallInt(i) => <u16 as Encode<'_, MySql>>::encode(*i, buf),
DataKind::UnsignedInt(i) => <u32 as Encode<'_, MySql>>::encode(*i, buf),
DataKind::UnsignedBigInt(i) => <u64 as Encode<'_, MySql>>::encode(*i, buf),
DataKind::Float(f) => <f32 as Encode<'_, MySql>>::encode(*f, buf),
DataKind::Double(d) => <f64 as Encode<'_, MySql>>::encode(*d, buf),
DataKind::Decimal(d) => <Decimal as Encode<'_, MySql>>::encode(*d, buf),
DataKind::Text(s) => <String as Encode<'_, MySql>>::encode(s.to_string(), buf),
DataKind::Blob(blob) => <Vec<u8> as Encode<'_, MySql>>::encode(blob.to_vec(), buf),
DataKind::Date(d) => <NaiveDate as Encode<'_, MySql>>::encode(*d, buf),
DataKind::Time(t) => <NaiveTime as Encode<'_, MySql>>::encode(*t, buf),
DataKind::DateTime(dt) => <NaiveDateTime as Encode<'_, MySql>>::encode(*dt, buf),
DataKind::Timestamp(ts) => <DateTime<Utc> as Encode<'_, MySql>>::encode(*ts, buf),
DataKind::Json(json) => {
let owned_json = Arc::clone(&json);
<Value as Encode<'_, MySql>>::encode(Arc::try_unwrap(owned_json)
.unwrap_or_else(|arc| (*arc).clone()), buf)
},
DataKind::Uuid(u) => <Uuid as Encode<'_, MySql>>::encode(*u, buf),
DataKind::IpAddr(ip) => <String as Encode<'_, MySql>>::encode(ip.to_string(), buf),
DataKind::Ipv4Addr(ipv4) => <String as Encode<'_, MySql>>::encode(ipv4.to_string(), buf),
DataKind::Ipv6Addr(ipv6) => <String as Encode<'_, MySql>>::encode(ipv6.to_string(), buf),
}
}
fn produces(&self) -> Option<MySqlTypeInfo> {
Some(self.get_type_info())
}
}
impl Type<MySql> for DataKind {
fn type_info() -> MySqlTypeInfo {
<str as Type<MySql>>::type_info()
}
fn compatible(ty: &MySqlTypeInfo) -> bool {
matches!(
ty.name(),
"TINYINT" | "SMALLINT" | "INT" | "BIGINT"
| "FLOAT" | "DOUBLE" | "DECIMAL"
| "CHAR" | "VARCHAR" | "TEXT" | "LONGTEXT"
| "BINARY" | "VARBINARY" | "BLOB" | "MEDIUMBLOB" | "LONGBLOB"
| "DATE" | "TIME" | "DATETIME" | "TIMESTAMP"
| "JSON" | "NULL" | "BOOLEAN" | "BOOL"
| "UUID"
)
}
}
impl DataKind {
pub fn get_type_info(&self) -> MySqlTypeInfo {
match self {
DataKind::Null => <str as Type<MySql>>::type_info(),
DataKind::Bool(_) => <bool as Type<MySql>>::type_info(),
DataKind::TinyInt(_) => <i8 as Type<MySql>>::type_info(),
DataKind::SmallInt(_) => <i16 as Type<MySql>>::type_info(),
DataKind::Int(_) => <i32 as Type<MySql>>::type_info(),
DataKind::BigInt(_) => <i64 as Type<MySql>>::type_info(),
DataKind::UnsignedTinyInt(_) => <u8 as Type<MySql>>::type_info(),
DataKind::UnsignedSmallInt(_) => <u16 as Type<MySql>>::type_info(),
DataKind::UnsignedInt(_) => <u32 as Type<MySql>>::type_info(),
DataKind::UnsignedBigInt(_) => <u64 as Type<MySql>>::type_info(),
DataKind::Float(_) => <f32 as Type<MySql>>::type_info(),
DataKind::Double(_) => <f64 as Type<MySql>>::type_info(),
DataKind::Decimal(_) => <Decimal as Type<MySql>>::type_info(),
DataKind::Text(_) => <str as Type<MySql>>::type_info(),
DataKind::Blob(_) => <Vec<u8> as Type<MySql>>::type_info(),
DataKind::Date(_) => <NaiveDate as Type<MySql>>::type_info(),
DataKind::Time(_) => <NaiveTime as Type<MySql>>::type_info(),
DataKind::DateTime(_) => <NaiveDateTime as Type<MySql>>::type_info(),
DataKind::Timestamp(_) => <DateTime<Utc> as Type<MySql>>::type_info(),
DataKind::Json(_) => <Value as Type<MySql>>::type_info(),
DataKind::Uuid(_) => <Uuid as Type<MySql>>::type_info(),
DataKind::IpAddr(_) => <String as Type<MySql>>::type_info(),
DataKind::Ipv4Addr(_) => <String as Type<MySql>>::type_info(),
DataKind::Ipv6Addr(_) => <String as Type<MySql>>::type_info(),
}
}
}
impl ValueConvert for DataKind {
fn convert(value: &dyn Any) -> Self {
macro_rules! try_convert {
($($type:ty => $variant:expr),*) => {
$(if let Some(v) = unwrap_option::<$type>(value) {
return $variant(v);
})*
return DataKind::Null;
};
}
try_convert!(
String => |v: &String| DataKind::Text(v.clone()),
&str => |v: &&str| DataKind::Text(v.to_string()),
i8 => |v: &i8| DataKind::TinyInt(*v),
i16 => |v: &i16| DataKind::SmallInt(*v),
i32 => |v: &i32| DataKind::Int(*v),
i64 => |v: &i64| DataKind::BigInt(*v),
u8 => |v: &u8| DataKind::UnsignedTinyInt(*v),
u16 => |v: &u16| DataKind::UnsignedSmallInt(*v),
u32 => |v: &u32| DataKind::UnsignedInt(*v),
u64 => |v: &u64| DataKind::UnsignedBigInt(*v),
f32 => |v: &f32| DataKind::Float(*v),
f64 => |v: &f64| DataKind::Double(*v),
NaiveDate => |v: &NaiveDate| DataKind::Date(*v),
NaiveTime => |v: &NaiveTime| DataKind::Time(*v),
NaiveDateTime => |v: &NaiveDateTime| DataKind::DateTime(*v),
DateTime<Utc> => |v: &DateTime<Utc>| DataKind::Timestamp(*v),
Vec<u8> => |v: &Vec<u8>| DataKind::Blob(Arc::from(v.as_slice())),
&[u8] => |v: &&[u8]| DataKind::Blob(Arc::from(*v)),
bool => |v: &bool| DataKind::Bool(*v),
Value => |v: &Value| DataKind::Json(Arc::new(v.clone())),
Uuid => |v: &Uuid| DataKind::Uuid(*v),
IpAddr => |v: &IpAddr| DataKind::IpAddr(*v),
Ipv4Addr => |v: &Ipv4Addr| DataKind::Ipv4Addr(*v),
Ipv6Addr => |v: &Ipv6Addr| DataKind::Ipv6Addr(*v)
);
}
fn is_default_value(value: &Self) -> bool {
match value {
DataKind::Int(v) => *v == 0,
DataKind::BigInt(v) => *v == 0,
DataKind::UnsignedInt(v) => *v == 0,
DataKind::UnsignedBigInt(v) => *v == 0,
DataKind::Uuid(v) => v.is_nil(),
DataKind::Text(v) => v.is_empty(),
_ => false,
}
}
}
macro_rules! impl_from {
($type:ty, $variant:expr) => {
impl From<$type> for DataKind {
fn from(item: $type) -> Self {
$variant(item)
}
}
};
}
impl_from!(String, |value: String| DataKind::Text(value));
impl_from!(&str, |value: &str| DataKind::Text(value.to_string()));
impl_from!(Vec<u8>, |value: Vec<u8>| DataKind::Blob(Arc::from(value)));
impl_from!(&[u8], |value: &[u8]| DataKind::Blob(Arc::from(value)));
impl_from!(i8, DataKind::TinyInt);
impl_from!(i16, DataKind::SmallInt);
impl_from!(i32, DataKind::Int);
impl_from!(i64, DataKind::BigInt);
impl_from!(u8, DataKind::UnsignedTinyInt);
impl_from!(u16, DataKind::UnsignedSmallInt);
impl_from!(u32, DataKind::UnsignedInt);
impl_from!(u64, DataKind::UnsignedBigInt);
impl_from!(f32, DataKind::Float);
impl_from!(f64, DataKind::Double);
impl_from!(bool, DataKind::Bool);
impl_from!(NaiveDate, DataKind::Date);
impl_from!(NaiveTime, DataKind::Time);
impl_from!(NaiveDateTime, DataKind::DateTime);
impl_from!(DateTime<Utc>, DataKind::Timestamp);
impl_from!(Value, |value: Value| DataKind::Json(Arc::new(value)));
impl_from!(Uuid, DataKind::Uuid);
impl_from!(IpAddr, DataKind::IpAddr);
impl_from!(Ipv4Addr, DataKind::Ipv4Addr);
impl_from!(Ipv6Addr, DataKind::Ipv6Addr);
impl<'a> From<DataKind> for Cow<'a, DataKind> {
fn from(value: DataKind) -> Self {
Cow::Owned(value)
}
}
impl<'a> From<&'a DataKind> for Cow<'a, DataKind> {
fn from(value: &'a DataKind) -> Self {
Cow::Borrowed(value)
}
}