use std::fmt;
use std::hash::Hash;
use std::hash::Hasher;
use std::sync::Arc;
use vortex_error::VortexExpect;
use vortex_error::VortexResult;
use vortex_error::vortex_ensure;
use crate::dtype::DType;
use crate::dtype::FieldDType;
use crate::dtype::Nullability;
#[allow(
clippy::derived_hash_with_manual_eq,
reason = "manual PartialEq adds Arc::ptr_eq fast path only"
)]
#[derive(Clone, Eq, Hash)]
pub struct MapDType(Arc<MapDTypeInner>);
impl PartialEq for MapDType {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0) || self.0 == other.0
}
}
impl fmt::Debug for MapDType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MapDType")
.field("key", &self.0.key)
.field("value", &self.0.value)
.field("keys_sorted", &self.0.keys_sorted)
.finish()
}
}
impl fmt::Display for MapDType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"map({}, {}, keys_sorted={})",
self.key_dtype(),
self.value_dtype(),
self.keys_sorted()
)
}
}
struct MapDTypeInner {
key: FieldDType,
value: FieldDType,
keys_sorted: bool,
}
impl PartialEq for MapDTypeInner {
fn eq(&self, other: &Self) -> bool {
self.key == other.key && self.value == other.value && self.keys_sorted == other.keys_sorted
}
}
impl Eq for MapDTypeInner {}
impl Hash for MapDTypeInner {
fn hash<H: Hasher>(&self, state: &mut H) {
self.key.hash(state);
self.value.hash(state);
self.keys_sorted.hash(state);
}
}
impl MapDType {
pub(crate) fn eq_ignore_nullability(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
|| (self.key_dtype().eq_ignore_nullability(&other.key_dtype())
&& self
.value_dtype()
.eq_ignore_nullability(&other.value_dtype())
&& self.keys_sorted() == other.keys_sorted())
}
pub(crate) fn hash_ignore_nullability<H: Hasher>(&self, state: &mut H) {
self.key_dtype().hash_ignore_nullability(state);
self.value_dtype().hash_ignore_nullability(state);
self.keys_sorted().hash(state);
}
pub fn try_new(key: DType, value: DType, keys_sorted: bool) -> VortexResult<Self> {
Self::try_from_fields(key.into(), value.into(), keys_sorted)
}
pub(crate) fn try_from_fields(
key: FieldDType,
value: FieldDType,
keys_sorted: bool,
) -> VortexResult<Self> {
vortex_ensure!(
!key.value()?.is_nullable(),
"map key dtype must be non-nullable"
);
Ok(Self(Arc::new(MapDTypeInner {
key,
value,
keys_sorted,
})))
}
pub fn key_dtype(&self) -> DType {
self.0
.key
.value()
.vortex_expect("map key dtype must be valid")
}
pub fn value_dtype(&self) -> DType {
self.0
.value
.value()
.vortex_expect("map value dtype must be valid")
}
pub fn keys_sorted(&self) -> bool {
self.0.keys_sorted
}
pub fn entries_dtype(&self) -> DType {
DType::struct_(
[("key", self.key_dtype()), ("value", self.value_dtype())],
Nullability::NonNullable,
)
}
}
#[cfg(test)]
mod tests {
use vortex_error::VortexResult;
use crate::dtype::DType;
use crate::dtype::MapDType;
use crate::dtype::Nullability;
use crate::dtype::PType;
#[test]
fn rejects_nullable_keys() {
let result = MapDType::try_new(
DType::Primitive(PType::I32, Nullability::Nullable),
DType::Utf8(Nullability::Nullable),
false,
);
assert!(result.is_err());
}
#[test]
fn accepts_nullable_values() -> VortexResult<()> {
let dtype = MapDType::try_new(
DType::Primitive(PType::I32, Nullability::NonNullable),
DType::Utf8(Nullability::Nullable),
true,
)?;
assert_eq!(
dtype.entries_dtype(),
DType::struct_(
[
(
"key",
DType::Primitive(PType::I32, Nullability::NonNullable)
),
("value", DType::Utf8(Nullability::Nullable)),
],
Nullability::NonNullable,
)
);
assert!(dtype.keys_sorted());
assert_eq!(dtype.to_string(), "map(i32, utf8?, keys_sorted=true)");
Ok(())
}
#[test]
fn outer_nullability_is_independent_of_map_details() -> VortexResult<()> {
let dtype = DType::map(
DType::Primitive(PType::I32, Nullability::NonNullable),
DType::Utf8(Nullability::Nullable),
true,
Nullability::NonNullable,
)?;
let nullable = dtype.as_nullable();
assert!(dtype.is_map());
assert!(!dtype.is_nullable());
assert!(nullable.is_nullable());
assert!(dtype.eq_ignore_nullability(&nullable));
assert_eq!(dtype.as_map_opt(), nullable.as_map_opt());
assert!(nullable.as_map_opt().unwrap().keys_sorted());
Ok(())
}
}