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_error::VortexExpect;
7use vortex_error::VortexResult;
8use vortex_error::vortex_bail;
9use vortex_error::vortex_ensure;
10use vortex_error::vortex_err;
11
12use crate::dtype::DType;
13use crate::scalar::Scalar;
14
15impl Scalar {
16    /// Cast this scalar to another data type.
17    ///
18    /// # Errors
19    ///
20    /// Returns an error if the cast is not supported or if a null value is cast to a non-nullable
21    /// type.
22    pub fn cast(&self, target_dtype: &DType) -> VortexResult<Scalar> {
23        // If the types are the same, return a clone.
24        if self.dtype() == target_dtype {
25            return Ok(self.clone());
26        }
27
28        // Check for solely nullability casting.
29        if self.dtype().eq_ignore_nullability(target_dtype) {
30            // Cast from non-nullable to nullable or vice versa.
31            // The `try_new` will handle nullability checks.
32            return Scalar::try_new(target_dtype.clone(), self.value().cloned());
33        }
34
35        if let (Some(source), Some(target)) = (self.dtype().as_map_opt(), target_dtype.as_map_opt())
36            && target.keys_sorted()
37            && !source.keys_sorted()
38        {
39            return Err(vortex_err!(
40                "Cannot cast {} to {target_dtype}: source does not assert sorted map keys",
41                self.dtype()
42            ));
43        }
44
45        // Null can be cast into any nullable type as null.
46        // Note that the `matches` clause is technically unnecessary here, just protective.
47        if self.value().is_none() || matches!(self.dtype(), DType::Null) {
48            vortex_ensure!(
49                target_dtype.is_nullable(),
50                "Cannot cast null to {target_dtype}: target type is non-nullable"
51            );
52
53            return Scalar::try_new(target_dtype.clone(), self.value().cloned());
54        }
55
56        // TODO(connor): This isn't really correct for extension types.
57        // If the target is an extension type, then we want to cast to its storage type.
58        if let Some(ext_dtype) = target_dtype.as_extension_opt() {
59            let cast_storage_scalar_value = self.cast(ext_dtype.storage_dtype())?.into_value();
60            return Scalar::try_new(target_dtype.clone(), cast_storage_scalar_value);
61        }
62
63        match &self.dtype() {
64            DType::Null => unreachable!("Handled by the if case above"),
65            DType::Bool(_) => self.as_bool().cast(target_dtype),
66            DType::Primitive(..) => self.as_primitive().cast(target_dtype),
67            DType::Decimal(..) => self.as_decimal().cast(target_dtype),
68            DType::Utf8(_) => self.as_utf8().cast(target_dtype),
69            DType::Binary(_) => self.as_binary().cast(target_dtype),
70            DType::List(..) | DType::FixedSizeList(..) => self.as_list().cast(target_dtype),
71            DType::Map(..) => self.as_map().cast(target_dtype),
72            DType::Struct(..) => self.as_struct().cast(target_dtype),
73            DType::Union(..) => vortex_bail!(
74                "union scalar cast from {} to {target_dtype} is not supported (yet)",
75                self.dtype()
76            ),
77            DType::Variant(_) => vortex_bail!("Variant scalars can't be cast to {target_dtype}"),
78            DType::Extension(..) => self.as_extension().cast(target_dtype),
79        }
80    }
81
82    /// Cast the scalar into a nullable version of its current type.
83    pub fn into_nullable(self) -> Scalar {
84        let (dtype, value) = self.into_parts();
85        Self::try_new(dtype.as_nullable(), value)
86            .vortex_expect("Casting to nullable should always succeed")
87    }
88}