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::VortexExpect;
5use vortex_error::VortexResult;
6use vortex_error::vortex_bail;
7use vortex_error::vortex_ensure;
8use vortex_error::vortex_ensure_eq;
9
10use crate::dtype::DType;
11use crate::dtype::PType;
12use crate::scalar::PValue;
13use crate::scalar::Scalar;
14use crate::scalar::ScalarValue;
15
16impl Scalar {
17    /// Validate that the given [`ScalarValue`] is compatible with the given [`DType`].
18    pub fn validate(dtype: &DType, value: Option<&ScalarValue>) -> VortexResult<()> {
19        let Some(value) = value else {
20            vortex_ensure!(
21                dtype.is_nullable(),
22                "non-nullable dtype {dtype} cannot hold a null value",
23            );
24            return Ok(());
25        };
26
27        // From here onwards, we know that the value is not null.
28        match dtype {
29            DType::Null => {
30                vortex_bail!("null dtype cannot hold a non-null value {value}");
31            }
32            DType::Bool(_) => {
33                vortex_ensure!(
34                    matches!(value, ScalarValue::Bool(_)),
35                    "bool dtype expected Bool value, got {value}",
36                );
37            }
38            DType::Primitive(ptype, _) => {
39                let ScalarValue::Primitive(pvalue) = value else {
40                    vortex_bail!("primitive dtype {ptype} expected Primitive value, got {value}",);
41                };
42
43                // Note that this is a backwards compatibility check for poor design in the
44                // previous implementation. `f16` `ScalarValue`s used to be serialized as
45                // `pb::ScalarValue::Uint64Value(v.to_bits() as u64)`, so we need to ensure
46                // that we can still represent them as such.
47                let f16_backcompat_still_works =
48                    matches!(ptype, &PType::F16) && matches!(pvalue, PValue::U64(_));
49
50                vortex_ensure!(
51                    f16_backcompat_still_works || pvalue.ptype() == *ptype,
52                    "primitive dtype {ptype} is not compatible with value {pvalue}",
53                );
54            }
55            DType::Decimal(dec_dtype, _) => {
56                let ScalarValue::Decimal(dvalue) = value else {
57                    vortex_bail!("decimal dtype expected Decimal value, got {value}");
58                };
59
60                vortex_ensure!(
61                    dvalue.fits_in_precision(*dec_dtype),
62                    "decimal value {dvalue} does not fit in precision of {dec_dtype}",
63                );
64            }
65            DType::Utf8(_) => {
66                vortex_ensure!(
67                    matches!(value, ScalarValue::Utf8(_)),
68                    "utf8 dtype expected Utf8 value, got {value}",
69                );
70            }
71            DType::Binary(_) => {
72                vortex_ensure!(
73                    matches!(value, ScalarValue::Binary(_)),
74                    "binary dtype expected Binary value, got {value}",
75                );
76            }
77            DType::List(elem_dtype, _) => {
78                let ScalarValue::Tuple(elements) = value else {
79                    vortex_bail!("list dtype expected Tuple value, got {value}");
80                };
81
82                for (i, element) in elements.iter().enumerate() {
83                    Self::validate(elem_dtype.as_ref(), element.as_ref())
84                        .map_err(|e| vortex_error::vortex_err!("list element at index {i}: {e}"))?;
85                }
86            }
87            DType::FixedSizeList(elem_dtype, size, _) => {
88                let ScalarValue::Tuple(elements) = value else {
89                    vortex_bail!("fixed-size list dtype expected Tuple value, got {value}",);
90                };
91
92                let len = elements.len();
93                vortex_ensure_eq!(
94                    len,
95                    *size as usize,
96                    "fixed-size list dtype expected {size} elements, got {len}",
97                );
98
99                for (i, element) in elements.iter().enumerate() {
100                    Self::validate(elem_dtype.as_ref(), element.as_ref()).map_err(|e| {
101                        vortex_error::vortex_err!("fixed-size list element at index {i}: {e}",)
102                    })?;
103                }
104            }
105            DType::Map(map, _) => {
106                let ScalarValue::Tuple(entries) = value else {
107                    vortex_bail!("map dtype expected Tuple value, got {value}");
108                };
109                let key_dtype = map.key_dtype();
110                let value_dtype = map.value_dtype();
111
112                for (index, entry) in entries.iter().enumerate() {
113                    let entry = entry.as_ref().ok_or_else(|| {
114                        vortex_error::vortex_err!("map entry at index {index} cannot be null")
115                    })?;
116                    let ScalarValue::Tuple(values) = entry else {
117                        vortex_bail!(
118                            "map entry at index {index} expected Tuple value, got {entry}"
119                        );
120                    };
121                    vortex_ensure_eq!(
122                        values.len(),
123                        2,
124                        "map entry at index {index} expected 2 values, got {}",
125                        values.len(),
126                    );
127
128                    Self::validate(&key_dtype, values[0].as_ref()).map_err(|error| {
129                        vortex_error::vortex_err!("map key at entry {index}: {error}")
130                    })?;
131                    Self::validate(&value_dtype, values[1].as_ref()).map_err(|error| {
132                        vortex_error::vortex_err!("map value at entry {index}: {error}")
133                    })?;
134                }
135            }
136            DType::Struct(fields, _) => {
137                let ScalarValue::Tuple(values) = value else {
138                    vortex_bail!("struct dtype expected Tuple value, got {value}");
139                };
140
141                let nfields = fields.nfields();
142                let nvalues = values.len();
143                vortex_ensure_eq!(
144                    nvalues,
145                    nfields,
146                    "struct dtype expected {nfields} fields, got {nvalues}",
147                );
148
149                for (field, field_value) in fields.fields().zip(values.iter()) {
150                    Self::validate(&field, field_value.as_ref())?;
151                }
152            }
153            DType::Union(variants, _) => {
154                let ScalarValue::Union(union_value) = value else {
155                    vortex_bail!("union dtype expected Union value, got {value}");
156                };
157
158                let type_id = union_value.type_id();
159                let Some(child_index) = variants.tag_to_child_index(type_id) else {
160                    vortex_bail!(
161                        "union value has unknown type ID {type_id}; expected one of {:?}",
162                        variants.type_ids()
163                    );
164                };
165
166                let child_dtype = variants
167                    .variant_by_index(child_index)
168                    .vortex_expect("resolved union child index must be valid");
169
170                Self::validate(&child_dtype, union_value.child_value()).map_err(|error| {
171                    vortex_error::vortex_err!(
172                        "union value for type ID {type_id} is invalid for dtype {child_dtype}: \
173                         {error}"
174                    )
175                })?;
176            }
177            DType::Variant(_) => {
178                let ScalarValue::Variant(inner) = value else {
179                    vortex_bail!("variant dtype expected Variant value, got {value}");
180                };
181
182                Self::validate(inner.dtype(), inner.value())?;
183                vortex_ensure!(
184                    !inner.is_null() || matches!(inner.dtype(), DType::Null),
185                    "variant nulls must use a nested null scalar, got {}",
186                    inner.dtype(),
187                );
188            }
189            DType::Extension(ext_dtype) => ext_dtype.validate_storage_value(value)?,
190        }
191
192        Ok(())
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use vortex_error::VortexResult;
199
200    use crate::dtype::DType;
201    use crate::dtype::Nullability;
202    use crate::dtype::PType;
203    use crate::dtype::UnionVariants;
204    use crate::scalar::Scalar;
205    use crate::scalar::ScalarValue;
206    use crate::scalar::UnionValue;
207
208    #[test]
209    fn union_rejects_unknown_tag_and_wrong_value() -> VortexResult<()> {
210        let variants = UnionVariants::try_new(
211            ["int", "string"].into(),
212            vec![
213                DType::Primitive(PType::I32, Nullability::Nullable),
214                DType::Utf8(Nullability::NonNullable),
215            ],
216            vec![5, 9],
217        )?;
218        let dtype = DType::Union(variants, Nullability::NonNullable);
219
220        assert!(
221            Scalar::try_new(
222                dtype.clone(),
223                Some(ScalarValue::Union(UnionValue::new(
224                    7,
225                    Scalar::primitive(42_i32, Nullability::Nullable).into_value(),
226                ))),
227            )
228            .is_err()
229        );
230
231        assert!(
232            Scalar::try_new(
233                dtype,
234                Some(ScalarValue::Union(UnionValue::new(
235                    5,
236                    Scalar::utf8("wrong", Nullability::NonNullable).into_value(),
237                ))),
238            )
239            .is_err()
240        );
241
242        Ok(())
243    }
244}