vortex_dtype/
extension.rs

1use std::fmt::{Display, Formatter};
2use std::sync::Arc;
3
4use crate::{DType, Nullability};
5
6/// A unique identifier for an extension type
7#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
8#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
9pub struct ExtID(Arc<str>);
10
11impl ExtID {
12    /// Constructs a new `ExtID` from a string
13    pub fn new(value: Arc<str>) -> Self {
14        Self(value)
15    }
16}
17
18impl Display for ExtID {
19    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
20        write!(f, "{}", self.0)
21    }
22}
23
24impl AsRef<str> for ExtID {
25    fn as_ref(&self) -> &str {
26        self.0.as_ref()
27    }
28}
29
30impl From<&str> for ExtID {
31    fn from(value: &str) -> Self {
32        Self(value.into())
33    }
34}
35
36/// Opaque metadata for an extension type
37#[derive(Debug, Clone, PartialOrd, PartialEq, Eq, Hash)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39pub struct ExtMetadata(Arc<[u8]>);
40
41impl ExtMetadata {
42    /// Constructs a new `ExtMetadata` from a byte slice
43    pub fn new(value: Arc<[u8]>) -> Self {
44        Self(value)
45    }
46}
47
48impl AsRef<[u8]> for ExtMetadata {
49    fn as_ref(&self) -> &[u8] {
50        self.0.as_ref()
51    }
52}
53
54impl From<&[u8]> for ExtMetadata {
55    fn from(value: &[u8]) -> Self {
56        Self(value.into())
57    }
58}
59
60/// A type descriptor for an extension type
61#[derive(Debug, Clone, PartialEq, Eq, Hash)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
63pub struct ExtDType {
64    id: ExtID,
65    storage_dtype: Arc<DType>,
66    metadata: Option<ExtMetadata>,
67}
68
69impl ExtDType {
70    /// Creates a new `ExtDType`.
71    ///
72    /// Extension data types in Vortex allows library users to express additional semantic meaning
73    /// on top of a set of scalar values. Metadata can optionally be provided for the extension type
74    /// to allow for parameterized types.
75    ///
76    /// A simple example would be if one wanted to create a `vortex.temperature` extension type. The
77    /// canonical encoding for such values would be `f64`, and the metadata can contain an optional
78    /// temperature unit, allowing downstream users to be sure they properly account for Celsius
79    /// and Fahrenheit conversions.
80    ///
81    /// ```
82    /// use std::sync::Arc;
83    /// use vortex_dtype::{DType, ExtDType, ExtID, ExtMetadata, Nullability, PType};
84    ///
85    /// #[repr(u8)]
86    /// enum TemperatureUnit {
87    ///     C = 0u8,
88    ///     F = 1u8,
89    /// }
90    ///
91    /// // Make a new extension type that encodes the unit for a set of nullable `f64`.
92    /// pub fn create_temperature_type(unit: TemperatureUnit) -> ExtDType {
93    ///     ExtDType::new(
94    ///         ExtID::new("vortex.temperature".into()),
95    ///         Arc::new(DType::Primitive(PType::F64, Nullability::Nullable)),
96    ///         Some(ExtMetadata::new([unit as u8].into()))
97    ///     )
98    /// }
99    /// ```
100    pub fn new(id: ExtID, storage_dtype: Arc<DType>, metadata: Option<ExtMetadata>) -> Self {
101        assert!(
102            !matches!(storage_dtype.as_ref(), &DType::Extension(_)),
103            "ExtDType cannot have Extension storage_dtype"
104        );
105
106        Self {
107            id,
108            storage_dtype,
109            metadata,
110        }
111    }
112
113    /// Returns the `ExtID` for this extension type
114    #[inline]
115    pub fn id(&self) -> &ExtID {
116        &self.id
117    }
118
119    /// Returns the `ExtMetadata` for this extension type, if it exists
120    #[inline]
121    pub fn storage_dtype(&self) -> &DType {
122        self.storage_dtype.as_ref()
123    }
124
125    /// Returns a new `ExtDType` with the given nullability
126    pub fn with_nullability(&self, nullability: Nullability) -> Self {
127        Self::new(
128            self.id.clone(),
129            Arc::new(self.storage_dtype.with_nullability(nullability)),
130            self.metadata.clone(),
131        )
132    }
133
134    /// Returns the `ExtMetadata` for this extension type, if it exists
135    #[inline]
136    pub fn metadata(&self) -> Option<&ExtMetadata> {
137        self.metadata.as_ref()
138    }
139
140    /// Check if `self` and `other` are equal, ignoring the storage nullability
141    pub fn eq_ignore_nullability(&self, other: &Self) -> bool {
142        self.id() == other.id()
143            && self.metadata() == other.metadata()
144            && self
145                .storage_dtype()
146                .eq_ignore_nullability(other.storage_dtype())
147    }
148}
149
150#[cfg(test)]
151mod test {
152    use std::sync::Arc;
153
154    use super::{ExtDType, ExtID};
155    use crate::{DType, Nullability, PType};
156
157    #[test]
158    fn different_ids_are_not_equal() {
159        let storage_dtype = Arc::from(DType::Bool(Nullability::NonNullable));
160        let one = ExtDType::new(ExtID::new(Arc::from("one")), storage_dtype.clone(), None);
161        let two = ExtDType::new(ExtID::new(Arc::from("two")), storage_dtype, None);
162
163        assert_ne!(one, two);
164    }
165
166    #[test]
167    fn same_id_different_storage_types_are_not_equal() {
168        let one = ExtDType::new(
169            ExtID::new(Arc::from("one")),
170            Arc::from(DType::Bool(Nullability::NonNullable)),
171            None,
172        );
173        let two = ExtDType::new(
174            ExtID::new(Arc::from("one")),
175            Arc::from(DType::Primitive(PType::U8, Nullability::NonNullable)),
176            None,
177        );
178
179        assert_ne!(one, two);
180    }
181
182    #[test]
183    fn same_id_different_nullability_are_not_equal() {
184        let nullable_u8 = Arc::from(DType::Primitive(PType::U8, Nullability::NonNullable));
185        let one = ExtDType::new(ExtID::new(Arc::from("one")), nullable_u8.clone(), None);
186        let two = ExtDType::new(
187            ExtID::new(Arc::from("one")),
188            Arc::from(nullable_u8.as_nullable()),
189            None,
190        );
191
192        assert_ne!(one, two);
193    }
194}