use super::TypeEnum;
use crate::{Result, driver, stmt};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Type {
Boolean,
Integer(u8),
UnsignedInteger(u8),
Float(u8),
Text,
VarChar(u64),
Uuid,
Numeric(#[cfg_attr(feature = "serde", serde(with = "numeric_serde"))] Option<(u32, u32)>),
Blob,
Binary(u8),
Timestamp(u8),
Date,
Time(u8),
DateTime(u8),
Cidr,
Inet,
MacAddr,
MacAddr8,
Enum(TypeEnum),
List(Box<Type>),
Document {
binary: bool,
},
Json,
Jsonb,
Custom(String),
}
#[cfg(feature = "serde")]
mod numeric_serde {
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _};
#[derive(Deserialize)]
#[serde(untagged)]
enum Repr {
Values(Vec<u32>),
Legacy(Option<(u32, u32)>),
}
pub fn serialize<S>(value: &Option<(u32, u32)>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some((precision, scale)) => [*precision, *scale].serialize(serializer),
None => <[u32; 0]>::default().serialize(serializer),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<(u32, u32)>, D::Error>
where
D: Deserializer<'de>,
{
match Repr::deserialize(deserializer)? {
Repr::Values(values) => match values.as_slice() {
[] => Ok(None),
[precision, scale] => Ok(Some((*precision, *scale))),
_ => Err(D::Error::custom(
"numeric storage type requires zero or two parameters",
)),
},
Repr::Legacy(value) => Ok(value),
}
}
}
impl Type {
pub fn list(elem: Type) -> Type {
match elem {
Type::Document { binary } => Type::Document { binary },
elem => Type::List(Box::new(elem)),
}
}
pub fn named_enum(&self) -> Option<&TypeEnum> {
match self {
Type::Enum(type_enum) => Some(type_enum),
Type::List(elem) => match elem.as_ref() {
Type::Enum(type_enum) => Some(type_enum),
_ => None,
},
_ => None,
}
}
pub fn named_enum_mut(&mut self) -> Option<&mut TypeEnum> {
match self {
Type::Enum(type_enum) => Some(type_enum),
Type::List(elem) => match elem.as_mut() {
Type::Enum(type_enum) => Some(type_enum),
_ => None,
},
_ => None,
}
}
pub fn from_app(
ty: &stmt::Type,
hint: Option<&Type>,
db: &driver::StorageTypes,
) -> Result<Type> {
match hint {
Some(ty) => Ok(ty.clone()),
None => match ty {
stmt::Type::Bool => Ok(Type::Boolean),
stmt::Type::I8 => Ok(Type::Integer(1)),
stmt::Type::I16 => Ok(Type::Integer(2)),
stmt::Type::I32 => Ok(Type::Integer(4)),
stmt::Type::I64 => Ok(Type::Integer(8)),
stmt::Type::U8 => Ok(Type::UnsignedInteger(1)),
stmt::Type::U16 => Ok(Type::UnsignedInteger(2)),
stmt::Type::U32 => Ok(Type::UnsignedInteger(4)),
stmt::Type::U64 => Ok(Type::UnsignedInteger(8)),
stmt::Type::F32 => Ok(Type::Float(4)),
stmt::Type::F64 => Ok(Type::Float(8)),
stmt::Type::String => Ok(db.default_string_type.clone()),
stmt::Type::Uuid => Ok(db.default_uuid_type.clone()),
stmt::Type::Bytes => Ok(db.default_bytes_type.clone()),
#[cfg(feature = "rust_decimal")]
stmt::Type::Decimal => Ok(db.default_decimal_type.clone()),
#[cfg(feature = "bigdecimal")]
stmt::Type::BigDecimal => Ok(db.default_bigdecimal_type.clone()),
#[cfg(feature = "jiff")]
stmt::Type::Timestamp => Ok(db.default_timestamp_type.clone()),
#[cfg(feature = "jiff")]
stmt::Type::Zoned => Ok(db.default_zoned_type.clone()),
#[cfg(feature = "jiff")]
stmt::Type::Date => Ok(db.default_date_type.clone()),
#[cfg(feature = "jiff")]
stmt::Type::Time => Ok(db.default_time_type.clone()),
#[cfg(feature = "jiff")]
stmt::Type::DateTime => Ok(db.default_datetime_type.clone()),
#[cfg(feature = "net")]
stmt::Type::Cidr => Ok(db.default_cidr_type.clone()),
#[cfg(feature = "net")]
stmt::Type::Inet => Ok(db.default_inet_type.clone()),
#[cfg(feature = "net")]
stmt::Type::MacAddr => Ok(db.default_macaddr_type.clone()),
#[cfg(feature = "net")]
stmt::Type::MacAddr8 => Ok(db.default_macaddr8_type.clone()),
stmt::Type::Model(_) => Ok(Type::Document { binary: true }),
stmt::Type::List(elem) => Ok(Type::list(Self::from_app(elem, None, db)?)),
_ => Err(crate::Error::unsupported_feature(format!(
"type {:?} is not supported by this database",
ty
))),
},
}
}
pub(crate) fn from_app_column(
ty: &stmt::Type,
hint: Option<&Type>,
db: &driver::Capability,
auto_increment: bool,
) -> Result<Type> {
let mut storage_ty = Self::from_app(ty, hint, &db.storage_types)?;
if auto_increment && let Some(max) = db.max_auto_increment_integer_width {
match &mut storage_ty {
Type::Integer(size) | Type::UnsignedInteger(size) if *size > max => {
*size = max;
}
_ => {}
}
}
Ok(storage_ty)
}
pub fn bridge_type(&self, ty: &stmt::Type) -> stmt::Type {
match (self, ty) {
(Self::List(storage), stmt::Type::List(app)) => {
stmt::Type::List(Box::new(storage.bridge_type(app)))
}
(Self::Blob | Self::Binary(_), stmt::Type::Uuid) => stmt::Type::Bytes,
(Self::Text | Self::VarChar(_), _) => stmt::Type::String,
(Self::Enum(_), _) => stmt::Type::String,
(Self::Integer(1), stmt::Type::I64) => stmt::Type::I8,
(Self::Integer(2), stmt::Type::I64) => stmt::Type::I16,
(Self::Integer(3..=4), stmt::Type::I64) => stmt::Type::I32,
(Self::UnsignedInteger(1), stmt::Type::I64) => stmt::Type::U8,
(Self::UnsignedInteger(2), stmt::Type::I64) => stmt::Type::U16,
(Self::UnsignedInteger(3..=4), stmt::Type::I64) => stmt::Type::U32,
(Self::UnsignedInteger(5..=8), stmt::Type::I64) => stmt::Type::U64,
#[cfg(feature = "jiff")]
(Self::Timestamp(_) | Self::DateTime(_), stmt::Type::Zoned) => stmt::Type::Timestamp,
(Self::Integer(1), stmt::Type::Bool) => stmt::Type::I8,
(Self::Document { .. }, stmt::Type::Model(_)) => stmt::Type::Object,
(Self::Document { .. }, stmt::Type::List(elem))
if matches!(**elem, stmt::Type::Model(_)) =>
{
stmt::Type::List(Box::new(stmt::Type::Object))
}
_ => ty.clone(),
}
}
pub(crate) fn verify(&self, db: &driver::Capability) -> Result<()> {
match *self {
Type::Json if !db.native_json => Err(crate::Error::unsupported_feature(format!(
"JSON column type is not supported by {}",
db.driver_name
))),
Type::Jsonb if !db.native_jsonb => Err(crate::Error::unsupported_feature(format!(
"JSONB column type is not supported by {}",
db.driver_name
))),
Type::VarChar(size) => match db.storage_types.varchar {
Some(max) if size > max => Err(crate::Error::unsupported_feature(format!(
"VARCHAR({}) exceeds database maximum of {}",
size, max
))),
None => Err(crate::Error::unsupported_feature(
"VARCHAR type is not supported by this database",
)),
_ => Ok(()),
},
Type::Cidr if !db.native_cidr => Err(crate::Error::unsupported_feature(format!(
"CIDR column type is not supported by {}",
db.driver_name
))),
Type::Inet if !db.native_inet => Err(crate::Error::unsupported_feature(format!(
"INET column type is not supported by {}",
db.driver_name
))),
Type::MacAddr if !db.native_macaddr => Err(crate::Error::unsupported_feature(format!(
"MACADDR column type is not supported by {}",
db.driver_name
))),
Type::MacAddr8 if !db.native_macaddr8 => {
Err(crate::Error::unsupported_feature(format!(
"MACADDR8 column type is not supported by {}",
db.driver_name
)))
}
_ => Ok(()),
}
}
}