Skip to main content

vortex_array/scalar/
cast.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Scalar casting between [`DType`]s.
5
6use vortex_dtype::DType;
7use vortex_error::VortexExpect;
8use vortex_error::VortexResult;
9use vortex_error::vortex_ensure;
10
11use crate::scalar::Scalar;
12
13impl Scalar {
14    /// Cast this scalar to another data type.
15    ///
16    /// # Errors
17    ///
18    /// Returns an error if the cast is not supported or if a null value is cast to a non-nullable
19    /// type.
20    pub fn cast(&self, target_dtype: &DType) -> VortexResult<Scalar> {
21        // If the types are the same, return a clone.
22        if self.dtype() == target_dtype {
23            return Ok(self.clone());
24        }
25
26        // Check for solely nullability casting.
27        if self.dtype().eq_ignore_nullability(target_dtype) {
28            // Cast from non-nullable to nullable or vice versa.
29            // The `try_new` will handle nullability checks.
30            return Scalar::try_new(target_dtype.clone(), self.value().cloned());
31        }
32
33        // Null can be cast into any nullable type as null.
34        // Note that the `matches` clause is technically unnecessary here, just protective.
35        if self.value().is_none() || matches!(self.dtype(), DType::Null) {
36            vortex_ensure!(
37                target_dtype.is_nullable(),
38                "Cannot cast null to {target_dtype}: target type is non-nullable"
39            );
40
41            return Scalar::try_new(target_dtype.clone(), self.value().cloned());
42        }
43
44        // TODO(connor): This isn't really correct but this will get fixed soon.
45        // If the target is an extension type, then we want to cast to its storage type.
46        if let Some(ext_dtype) = target_dtype.as_extension_opt() {
47            let cast_storage_scalar_value = self.cast(ext_dtype.storage_dtype())?.into_value();
48            return Scalar::try_new(target_dtype.clone(), cast_storage_scalar_value);
49        }
50
51        match &self.dtype() {
52            DType::Null => unreachable!("Handled by the if case above"),
53            DType::Bool(_) => self.as_bool().cast(target_dtype),
54            DType::Primitive(..) => self.as_primitive().cast(target_dtype),
55            DType::Decimal(..) => self.as_decimal().cast(target_dtype),
56            DType::Utf8(_) => self.as_utf8().cast(target_dtype),
57            DType::Binary(_) => self.as_binary().cast(target_dtype),
58            DType::Struct(..) => self.as_struct().cast(target_dtype),
59            DType::List(..) | DType::FixedSizeList(..) => self.as_list().cast(target_dtype),
60            DType::Extension(..) => self.as_extension().cast(target_dtype),
61        }
62    }
63
64    /// Cast the scalar into a nullable version of its current type.
65    pub fn into_nullable(self) -> Scalar {
66        let (dtype, value) = self.into_parts();
67        Self::try_new(dtype.as_nullable(), value)
68            .vortex_expect("Casting to nullable should always succeed")
69    }
70}