vortex_compute/cast/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Lossless casting of Vortex vectors and scalars for different logical data types.
5
6mod binaryview;
7mod bool;
8mod decimal;
9mod dvector;
10mod fixed_size_list;
11mod list;
12mod null;
13mod primitive;
14mod pvector;
15mod struct_;
16
17use vortex_dtype::DType;
18use vortex_error::VortexResult;
19use vortex_vector::Datum;
20use vortex_vector::Scalar;
21use vortex_vector::ScalarOps;
22use vortex_vector::Vector;
23use vortex_vector::VectorOps;
24use vortex_vector::match_each_scalar;
25use vortex_vector::match_each_vector;
26use vortex_vector::null::NullScalar;
27use vortex_vector::null::NullVector;
28
29/// Trait for casting vectors and scalars to different data types.
30///
31/// # Nullability Requirements
32///
33/// Casting a source that contains null values to a non-nullable target dtype will return an error.
34/// This invariant is not checked by the common helper functions, so each implementation is
35/// responsible for enforcing it.
36///
37/// # Common Casting Behaviors
38///
39/// All implementations share these behaviors:
40/// - **Identity**: Casting to the same dtype (with compatible nullability) returns a clone.
41/// - **Null casting**: Any all-null vector can be cast to [`DType::Null`], and any null scalar
42///   can be cast to a [`NullScalar`].
43/// - **Extension types**: Casting to an extension type delegates to casting to its storage dtype.
44pub trait Cast {
45    /// The output type after casting.
46    type Output;
47
48    /// Cast the vector or scalar to the specified data type.
49    fn cast(&self, target_dtype: &DType) -> VortexResult<Self::Output>;
50}
51
52impl Cast for Datum {
53    type Output = Datum;
54
55    /// Dispatches to the contained [`Scalar`] or [`Vector`] cast implementation.
56    fn cast(&self, target_dtype: &DType) -> VortexResult<Datum> {
57        Ok(match self {
58            Datum::Scalar(scalar) => scalar.cast(target_dtype)?.into(),
59            Datum::Vector(vector) => vector.cast(target_dtype)?.into(),
60        })
61    }
62}
63
64impl Cast for Scalar {
65    type Output = Scalar;
66
67    /// Dispatches to the underlying typed scalar implementation.
68    fn cast(&self, target_dtype: &DType) -> VortexResult<Scalar> {
69        match_each_scalar!(self, |s| { Cast::cast(s, target_dtype) })
70    }
71}
72
73impl Cast for Vector {
74    type Output = Vector;
75
76    /// Dispatches to the underlying typed vector implementation.
77    fn cast(&self, target_dtype: &DType) -> VortexResult<Vector> {
78        match_each_vector!(self, |v| { Cast::cast(v, target_dtype) })
79    }
80}
81
82/// Handles common vector cast cases: Null (if all-null) and Extension (delegate to storage).
83///
84/// Returns `Ok(Some(...))` if handled, `Err(...)` on error, or `Ok(None)` to fall through.
85pub(crate) fn try_cast_vector_common<V: Cast<Output = Vector> + VectorOps>(
86    vector: &V,
87    target_dtype: &DType,
88) -> VortexResult<Option<Vector>> {
89    match target_dtype {
90        DType::Null if vector.validity().all_false() => {
91            Ok(Some(NullVector::new(vector.len()).into()))
92        }
93        DType::Extension(ext_dtype) => vector.cast(ext_dtype.storage_dtype()).map(Some),
94        _ => Ok(None),
95    }
96}
97
98/// Handles common scalar cast cases: Null (if null) and Extension (delegate to storage).
99///
100/// Returns `Ok(Some(...))` if handled, `Err(...)` on error, or `Ok(None)` to fall through.
101pub(crate) fn try_cast_scalar_common<S: Cast<Output = Scalar> + ScalarOps>(
102    scalar: &S,
103    target_dtype: &DType,
104) -> VortexResult<Option<Scalar>> {
105    match target_dtype {
106        DType::Null if !scalar.is_valid() => Ok(Some(NullScalar.into())),
107        DType::Extension(ext_dtype) => scalar.cast(ext_dtype.storage_dtype()).map(Some),
108        _ => Ok(None),
109    }
110}