use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::hash::Hash;
use std::sync::Arc;
use DType::*;
use itertools::Itertools;
use vortex_error::VortexExpect;
use vortex_error::vortex_panic;
use crate::FieldDType;
use crate::FieldName;
use crate::PType;
use crate::StructFields;
use crate::decimal::DecimalDType;
use crate::decimal::DecimalType;
use crate::extension::ExtDTypeRef;
use crate::nullability::Nullability;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum DType {
Null,
Bool(Nullability),
Primitive(PType, Nullability),
Decimal(DecimalDType, Nullability),
Utf8(Nullability),
Binary(Nullability),
List(Arc<DType>, Nullability),
FixedSizeList(Arc<DType>, u32, Nullability),
Struct(StructFields, Nullability),
Extension(ExtDTypeRef),
}
pub trait NativeDType {
fn dtype() -> DType;
}
#[cfg(not(target_arch = "wasm32"))]
const _: [(); size_of::<DType>()] = [(); 24];
#[cfg(target_arch = "wasm32")]
const _: [(); size_of::<DType>()] = [(); 12];
impl DType {
pub const BYTES: Self = Primitive(PType::U8, Nullability::NonNullable);
#[inline]
pub fn nullability(&self) -> Nullability {
self.is_nullable().into()
}
#[inline]
pub fn is_nullable(&self) -> bool {
match self {
Null => true,
Extension(ext_dtype) => ext_dtype.storage_dtype().is_nullable(),
Bool(null)
| Primitive(_, null)
| Decimal(_, null)
| Utf8(null)
| Binary(null)
| Struct(_, null)
| List(_, null)
| FixedSizeList(_, _, null) => matches!(null, Nullability::Nullable),
}
}
pub fn as_nonnullable(&self) -> Self {
self.with_nullability(Nullability::NonNullable)
}
pub fn as_nullable(&self) -> Self {
self.with_nullability(Nullability::Nullable)
}
pub fn with_nullability(&self, nullability: Nullability) -> Self {
match self {
Null => Null,
Bool(_) => Bool(nullability),
Primitive(pdt, _) => Primitive(*pdt, nullability),
Decimal(ddt, _) => Decimal(*ddt, nullability),
Utf8(_) => Utf8(nullability),
Binary(_) => Binary(nullability),
Struct(sf, _) => Struct(sf.clone(), nullability),
List(edt, _) => List(edt.clone(), nullability),
FixedSizeList(edt, size, _) => FixedSizeList(edt.clone(), *size, nullability),
Extension(ext) => Extension(ext.with_nullability(nullability)),
}
}
pub fn union_nullability(&self, other: Nullability) -> Self {
let nullability = self.nullability() | other;
self.with_nullability(nullability)
}
pub fn eq_ignore_nullability(&self, other: &Self) -> bool {
match (self, other) {
(Null, Null) => true,
(Bool(_), Bool(_)) => true,
(Primitive(lhs_ptype, _), Primitive(rhs_ptype, _)) => lhs_ptype == rhs_ptype,
(Decimal(lhs, _), Decimal(rhs, _)) => lhs == rhs,
(Utf8(_), Utf8(_)) => true,
(Binary(_), Binary(_)) => true,
(List(lhs_dtype, _), List(rhs_dtype, _)) => lhs_dtype.eq_ignore_nullability(rhs_dtype),
(FixedSizeList(lhs_dtype, lhs_size, _), FixedSizeList(rhs_dtype, rhs_size, _)) => {
lhs_size == rhs_size && lhs_dtype.eq_ignore_nullability(rhs_dtype)
}
(Struct(lhs_dtype, _), Struct(rhs_dtype, _)) => {
(lhs_dtype.names() == rhs_dtype.names())
&& (lhs_dtype
.fields()
.zip_eq(rhs_dtype.fields())
.all(|(l, r)| l.eq_ignore_nullability(&r)))
}
(Extension(lhs_extdtype), Extension(rhs_extdtype)) => {
lhs_extdtype.eq_ignore_nullability(rhs_extdtype)
}
_ => false,
}
}
pub fn eq_with_nullability_subset(&self, other: &Self) -> bool {
if self.is_nullable() {
self == other
} else {
self.eq_ignore_nullability(other)
}
}
pub fn eq_with_nullability_superset(&self, other: &Self) -> bool {
if self.is_nullable() {
self.eq_ignore_nullability(other)
} else {
self == other
}
}
pub fn is_boolean(&self) -> bool {
matches!(self, Bool(_))
}
pub fn is_primitive(&self) -> bool {
matches!(self, Primitive(_, _))
}
pub fn as_ptype(&self) -> PType {
if let Primitive(ptype, _) = self {
*ptype
} else {
vortex_panic!("DType {self} is not a primitive type")
}
}
pub fn is_unsigned_int(&self) -> bool {
if let Primitive(ptype, _) = self {
return ptype.is_unsigned_int();
}
false
}
pub fn is_signed_int(&self) -> bool {
if let Primitive(ptype, _) = self {
return ptype.is_signed_int();
}
false
}
pub fn is_int(&self) -> bool {
if let Primitive(ptype, _) = self {
return ptype.is_int();
}
false
}
pub fn is_float(&self) -> bool {
if let Primitive(ptype, _) = self {
return ptype.is_float();
}
false
}
pub fn is_decimal(&self) -> bool {
matches!(self, Decimal(..))
}
pub fn is_utf8(&self) -> bool {
matches!(self, Utf8(_))
}
pub fn is_binary(&self) -> bool {
matches!(self, Binary(_))
}
pub fn is_list(&self) -> bool {
matches!(self, List(_, _))
}
pub fn is_fixed_size_list(&self) -> bool {
matches!(self, FixedSizeList(..))
}
pub fn is_struct(&self) -> bool {
matches!(self, Struct(_, _))
}
pub fn is_extension(&self) -> bool {
matches!(self, Extension(_))
}
pub fn is_nested(&self) -> bool {
match self {
List(..) | FixedSizeList(..) | Struct(..) => true,
Extension(ext) => ext.storage_dtype().is_nested(),
_ => false,
}
}
pub fn element_size(&self) -> Option<usize> {
match self {
Null => Some(0),
Bool(_) => Some(1),
Primitive(ptype, _) => Some(ptype.byte_width()),
Decimal(decimal, _) => {
Some(DecimalType::smallest_decimal_value_type(decimal).byte_width())
}
Utf8(_) | Binary(_) | List(..) => None,
FixedSizeList(elem_dtype, list_size, _) => {
elem_dtype.element_size().map(|s| s * *list_size as usize)
}
Struct(fields, ..) => {
let mut sum = 0_usize;
for f in fields.fields() {
let element_size = f.element_size()?;
sum = sum
.checked_add(element_size)
.vortex_expect("sum of field sizes is bigger than usize");
}
Some(sum)
}
Extension(ext) => ext.storage_dtype().element_size(),
}
}
pub fn as_decimal_opt(&self) -> Option<&DecimalDType> {
if let Decimal(decimal, _) = self {
Some(decimal)
} else {
None
}
}
pub fn into_decimal_opt(self) -> Option<DecimalDType> {
if let Decimal(decimal, _) = self {
Some(decimal)
} else {
None
}
}
pub fn as_list_element_opt(&self) -> Option<&Arc<DType>> {
if let List(edt, _) = self {
Some(edt)
} else {
None
}
}
pub fn into_list_element_opt(self) -> Option<Arc<DType>> {
if let List(edt, _) = self {
Some(edt)
} else {
None
}
}
pub fn as_fixed_size_list_element_opt(&self) -> Option<&Arc<DType>> {
if let FixedSizeList(edt, ..) = self {
Some(edt)
} else {
None
}
}
pub fn into_fixed_size_list_element_opt(self) -> Option<Arc<DType>> {
if let FixedSizeList(edt, ..) = self {
Some(edt)
} else {
None
}
}
pub fn as_any_size_list_element_opt(&self) -> Option<&Arc<DType>> {
if let FixedSizeList(edt, ..) = self {
Some(edt)
} else if let List(edt, ..) = self {
Some(edt)
} else {
None
}
}
pub fn into_any_size_list_element_opt(self) -> Option<Arc<DType>> {
if let FixedSizeList(edt, ..) = self {
Some(edt)
} else if let List(edt, ..) = self {
Some(edt)
} else {
None
}
}
pub fn as_struct_fields(&self) -> &StructFields {
if let Struct(f, _) = self {
return f;
}
vortex_panic!("DType is not a Struct")
}
pub fn into_struct_fields(self) -> StructFields {
if let Struct(f, _) = self {
return f;
}
vortex_panic!("DType is not a Struct")
}
pub fn as_struct_fields_opt(&self) -> Option<&StructFields> {
if let Struct(f, _) = self {
Some(f)
} else {
None
}
}
pub fn into_struct_fields_opt(self) -> Option<StructFields> {
if let Struct(f, _) = self {
Some(f)
} else {
None
}
}
pub fn as_extension(&self) -> &ExtDTypeRef {
let Extension(ext) = self else {
vortex_panic!("DType is not an Extension")
};
ext
}
pub fn as_extension_opt(&self) -> Option<&ExtDTypeRef> {
if let Extension(ext) = self {
Some(ext)
} else {
None
}
}
pub fn list(dtype: impl Into<DType>, nullability: Nullability) -> Self {
List(Arc::new(dtype.into()), nullability)
}
pub fn struct_<I: IntoIterator<Item = (impl Into<FieldName>, impl Into<FieldDType>)>>(
iter: I,
nullability: Nullability,
) -> Self {
Struct(StructFields::from_iter(iter), nullability)
}
}
impl Display for DType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Null => write!(f, "null"),
Bool(null) => write!(f, "bool{null}"),
Primitive(pdt, null) => write!(f, "{pdt}{null}"),
Decimal(ddt, null) => write!(f, "{ddt}{null}"),
Utf8(null) => write!(f, "utf8{null}"),
Binary(null) => write!(f, "binary{null}"),
Struct(sf, null) => write!(
f,
"{{{}}}{null}",
sf.names()
.iter()
.zip(sf.fields())
.map(|(field_null, dt)| format!("{field_null}={dt}"))
.join(", "),
),
List(edt, null) => write!(f, "list({edt}){null}"),
FixedSizeList(edt, size, null) => write!(f, "fixed_size_list({edt})[{size}]{null}"),
Extension(ext) => write!(f, "{}", ext),
}
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::DType;
use crate::Nullability::NonNullable;
use crate::Nullability::Nullable;
use crate::PType;
use crate::datetime::Date;
use crate::datetime::Time;
use crate::datetime::TimeUnit;
use crate::datetime::Timestamp;
use crate::decimal::DecimalDType;
#[test]
fn test_ext_dtype_eq_ignore_nullability() {
let d1 = DType::Extension(Time::new(TimeUnit::Seconds, Nullable).erased());
let d2 = DType::Extension(Time::new(TimeUnit::Seconds, NonNullable).erased());
assert!(d1.eq_ignore_nullability(&d2));
let t1 = DType::Extension(
Timestamp::new_with_tz(TimeUnit::Seconds, Some("UTC".into()), Nullable).erased(),
);
let t2 = DType::Extension(
Timestamp::new_with_tz(TimeUnit::Seconds, Some("ET".into()), Nullable).erased(),
);
assert!(!t1.eq_ignore_nullability(&t2));
}
#[test]
fn element_size_null() {
assert_eq!(DType::Null.element_size(), Some(0));
}
#[test]
fn element_size_bool() {
assert_eq!(DType::Bool(NonNullable).element_size(), Some(1));
}
#[test]
fn element_size_primitives() {
assert_eq!(
DType::Primitive(PType::U8, NonNullable).element_size(),
Some(1)
);
assert_eq!(
DType::Primitive(PType::I32, NonNullable).element_size(),
Some(4)
);
assert_eq!(
DType::Primitive(PType::F64, NonNullable).element_size(),
Some(8)
);
}
#[test]
fn element_size_decimal() {
let decimal = DecimalDType::new(10, 2);
assert_eq!(DType::Decimal(decimal, NonNullable).element_size(), Some(8));
}
#[test]
fn element_size_fixed_size_list() {
let elem = Arc::new(DType::Primitive(PType::F64, NonNullable));
assert_eq!(
DType::FixedSizeList(elem.clone(), 1000, NonNullable).element_size(),
Some(8000)
);
assert_eq!(
DType::FixedSizeList(
Arc::new(DType::FixedSizeList(elem, 20, NonNullable)),
1000,
NonNullable
)
.element_size(),
Some(160_000)
);
}
#[test]
fn element_size_nested_fixed_size_list() {
let inner = Arc::new(DType::FixedSizeList(
Arc::new(DType::Primitive(PType::F64, NonNullable)),
10,
NonNullable,
));
assert_eq!(
DType::FixedSizeList(inner, 100, NonNullable).element_size(),
Some(8000)
);
}
#[test]
fn element_size_extension() {
assert_eq!(
DType::Extension(Date::new(TimeUnit::Days, NonNullable).erased()).element_size(),
Some(4)
);
}
#[test]
fn element_size_variable_width() {
assert_eq!(DType::Utf8(NonNullable).element_size(), None);
assert_eq!(DType::Binary(NonNullable).element_size(), None);
assert_eq!(
DType::List(
Arc::new(DType::Primitive(PType::I32, NonNullable)),
NonNullable
)
.element_size(),
None
);
}
}