Skip to main content

vortex_array/scalar/
validate.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use vortex_error::VortexResult;
5use vortex_error::vortex_bail;
6use vortex_error::vortex_ensure;
7use vortex_error::vortex_ensure_eq;
8
9use crate::dtype::DType;
10use crate::dtype::PType;
11use crate::scalar::PValue;
12use crate::scalar::Scalar;
13use crate::scalar::ScalarValue;
14
15impl Scalar {
16    /// Validate that the given [`ScalarValue`] is compatible with the given [`DType`].
17    pub fn validate(dtype: &DType, value: Option<&ScalarValue>) -> VortexResult<()> {
18        let Some(value) = value else {
19            vortex_ensure!(
20                dtype.is_nullable(),
21                "non-nullable dtype {dtype} cannot hold a null value",
22            );
23            return Ok(());
24        };
25
26        // From here onwards, we know that the value is not null.
27        match dtype {
28            DType::Null => {
29                vortex_bail!("null dtype cannot hold a non-null value {value}");
30            }
31            DType::Bool(_) => {
32                vortex_ensure!(
33                    matches!(value, ScalarValue::Bool(_)),
34                    "bool dtype expected Bool value, got {value}",
35                );
36            }
37            DType::Primitive(ptype, _) => {
38                let ScalarValue::Primitive(pvalue) = value else {
39                    vortex_bail!("primitive dtype {ptype} expected Primitive value, got {value}",);
40                };
41
42                // Note that this is a backwards compatibility check for poor design in the
43                // previous implementation. `f16` `ScalarValue`s used to be serialized as
44                // `pb::ScalarValue::Uint64Value(v.to_bits() as u64)`, so we need to ensure
45                // that we can still represent them as such.
46                let f16_backcompat_still_works =
47                    matches!(ptype, &PType::F16) && matches!(pvalue, PValue::U64(_));
48
49                vortex_ensure!(
50                    f16_backcompat_still_works || pvalue.ptype() == *ptype,
51                    "primitive dtype {ptype} is not compatible with value {pvalue}",
52                );
53            }
54            DType::Decimal(dec_dtype, _) => {
55                let ScalarValue::Decimal(dvalue) = value else {
56                    vortex_bail!("decimal dtype expected Decimal value, got {value}");
57                };
58
59                vortex_ensure!(
60                    dvalue.fits_in_precision(*dec_dtype),
61                    "decimal value {dvalue} does not fit in precision of {dec_dtype}",
62                );
63            }
64            DType::Utf8(_) => {
65                vortex_ensure!(
66                    matches!(value, ScalarValue::Utf8(_)),
67                    "utf8 dtype expected Utf8 value, got {value}",
68                );
69            }
70            DType::Binary(_) => {
71                vortex_ensure!(
72                    matches!(value, ScalarValue::Binary(_)),
73                    "binary dtype expected Binary value, got {value}",
74                );
75            }
76            DType::List(elem_dtype, _) => {
77                let ScalarValue::List(elements) = value else {
78                    vortex_bail!("list dtype expected List value, got {value}");
79                };
80
81                for (i, element) in elements.iter().enumerate() {
82                    Self::validate(elem_dtype.as_ref(), element.as_ref())
83                        .map_err(|e| vortex_error::vortex_err!("list element at index {i}: {e}"))?;
84                }
85            }
86            DType::FixedSizeList(elem_dtype, size, _) => {
87                let ScalarValue::List(elements) = value else {
88                    vortex_bail!("fixed-size list dtype expected List value, got {value}",);
89                };
90
91                let len = elements.len();
92                vortex_ensure_eq!(
93                    len,
94                    *size as usize,
95                    "fixed-size list dtype expected {size} elements, got {len}",
96                );
97
98                for (i, element) in elements.iter().enumerate() {
99                    Self::validate(elem_dtype.as_ref(), element.as_ref()).map_err(|e| {
100                        vortex_error::vortex_err!("fixed-size list element at index {i}: {e}",)
101                    })?;
102                }
103            }
104            DType::Struct(fields, _) => {
105                let ScalarValue::List(values) = value else {
106                    vortex_bail!("struct dtype expected List value, got {value}");
107                };
108
109                let nfields = fields.nfields();
110                let nvalues = values.len();
111                vortex_ensure_eq!(
112                    nvalues,
113                    nfields,
114                    "struct dtype expected {nfields} fields, got {nvalues}",
115                );
116
117                for (field, field_value) in fields.fields().zip(values.iter()) {
118                    Self::validate(&field, field_value.as_ref())?;
119                }
120            }
121            DType::Extension(ext_dtype) => ext_dtype.validate_storage_value(value)?,
122        }
123
124        Ok(())
125    }
126}