vortex_array/scalar/
validate.rs1use 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 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 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 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::Struct(fields, _) => {
106 let ScalarValue::Tuple(values) = value else {
107 vortex_bail!("struct dtype expected Tuple value, got {value}");
108 };
109
110 let nfields = fields.nfields();
111 let nvalues = values.len();
112 vortex_ensure_eq!(
113 nvalues,
114 nfields,
115 "struct dtype expected {nfields} fields, got {nvalues}",
116 );
117
118 for (field, field_value) in fields.fields().zip(values.iter()) {
119 Self::validate(&field, field_value.as_ref())?;
120 }
121 }
122 DType::Union(variants, _) => {
123 let ScalarValue::Union(union_value) = value else {
124 vortex_bail!("union dtype expected Union value, got {value}");
125 };
126
127 let type_id = union_value.type_id();
128 let Some(child_index) = variants.tag_to_child_index(type_id) else {
129 vortex_bail!(
130 "union value has unknown type ID {type_id}; expected one of {:?}",
131 variants.type_ids()
132 );
133 };
134
135 let child_dtype = variants
136 .variant_by_index(child_index)
137 .vortex_expect("resolved union child index must be valid");
138
139 Self::validate(&child_dtype, union_value.child_value()).map_err(|error| {
140 vortex_error::vortex_err!(
141 "union value for type ID {type_id} is invalid for dtype {child_dtype}: \
142 {error}"
143 )
144 })?;
145 }
146 DType::Variant(_) => {
147 let ScalarValue::Variant(inner) = value else {
148 vortex_bail!("variant dtype expected Variant value, got {value}");
149 };
150
151 Self::validate(inner.dtype(), inner.value())?;
152 vortex_ensure!(
153 !inner.is_null() || matches!(inner.dtype(), DType::Null),
154 "variant nulls must use a nested null scalar, got {}",
155 inner.dtype(),
156 );
157 }
158 DType::Extension(ext_dtype) => ext_dtype.validate_storage_value(value)?,
159 }
160
161 Ok(())
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use vortex_error::VortexResult;
168
169 use crate::dtype::DType;
170 use crate::dtype::Nullability;
171 use crate::dtype::PType;
172 use crate::dtype::UnionVariants;
173 use crate::scalar::Scalar;
174 use crate::scalar::ScalarValue;
175 use crate::scalar::UnionValue;
176
177 #[test]
178 fn union_rejects_unknown_tag_and_wrong_value() -> VortexResult<()> {
179 let variants = UnionVariants::try_new(
180 ["int", "string"].into(),
181 vec![
182 DType::Primitive(PType::I32, Nullability::Nullable),
183 DType::Utf8(Nullability::NonNullable),
184 ],
185 vec![5, 9],
186 )?;
187 let dtype = DType::Union(variants, Nullability::NonNullable);
188
189 assert!(
190 Scalar::try_new(
191 dtype.clone(),
192 Some(ScalarValue::Union(UnionValue::new(
193 7,
194 Scalar::primitive(42_i32, Nullability::Nullable).into_value(),
195 ))),
196 )
197 .is_err()
198 );
199
200 assert!(
201 Scalar::try_new(
202 dtype,
203 Some(ScalarValue::Union(UnionValue::new(
204 5,
205 Scalar::utf8("wrong", Nullability::NonNullable).into_value(),
206 ))),
207 )
208 .is_err()
209 );
210
211 Ok(())
212 }
213}