Skip to content

Commit 0139936

Browse files
committed
refactor vortex-scalar tests and add more
Signed-off-by: Connor Tsui <[email protected]>
1 parent 7e65dd2 commit 0139936

6 files changed

Lines changed: 3191 additions & 1296 deletions

File tree

‎vortex-scalar/src/tests/casting.rs‎

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
//! Tests for type casting and coercion between different scalar types.
5+
6+
#[cfg(test)]
7+
mod tests {
8+
use std::sync::Arc;
9+
10+
use vortex_dtype::half::f16;
11+
use vortex_dtype::{DType, ExtDType, ExtID, FieldDType, Nullability, PType, StructFields};
12+
use vortex_error::VortexExpect;
13+
14+
use crate::{InnerScalarValue, PValue, Scalar, ScalarValue};
15+
16+
#[test]
17+
fn cast_to_from_extension_types() {
18+
let apples = ExtDType::new(
19+
ExtID::new(Arc::from("apples")),
20+
Arc::from(DType::Primitive(PType::U16, Nullability::NonNullable)),
21+
None,
22+
);
23+
let ext_dtype = DType::Extension(Arc::from(apples.clone()));
24+
let ext_scalar = Scalar::new(ext_dtype.clone(), ScalarValue(InnerScalarValue::Bool(true)));
25+
let storage_scalar = Scalar::new(
26+
DType::clone(apples.storage_dtype()),
27+
ScalarValue(InnerScalarValue::Primitive(PValue::U16(1000))),
28+
);
29+
30+
// to self
31+
let expected_dtype = &ext_dtype;
32+
let actual = ext_scalar.cast(expected_dtype).unwrap();
33+
assert_eq!(actual.dtype(), expected_dtype);
34+
35+
// to nullable self
36+
let expected_dtype = &ext_dtype.as_nullable();
37+
let actual = ext_scalar.cast(expected_dtype).unwrap();
38+
assert_eq!(actual.dtype(), expected_dtype);
39+
40+
// cast to the storage type
41+
let expected_dtype = apples.storage_dtype();
42+
let actual = ext_scalar.cast(expected_dtype).unwrap();
43+
assert_eq!(actual.dtype(), expected_dtype);
44+
45+
// cast to the storage type, nullable
46+
let expected_dtype = &apples.storage_dtype().as_nullable();
47+
let actual = ext_scalar.cast(expected_dtype).unwrap();
48+
assert_eq!(actual.dtype(), expected_dtype);
49+
50+
// cast from storage type to extension
51+
let expected_dtype = &ext_dtype;
52+
let actual = storage_scalar.cast(expected_dtype).unwrap();
53+
assert_eq!(actual.dtype(), expected_dtype);
54+
55+
// cast from storage type to extension, nullable
56+
let expected_dtype = &ext_dtype.as_nullable();
57+
let actual = storage_scalar.cast(expected_dtype).unwrap();
58+
assert_eq!(actual.dtype(), expected_dtype);
59+
60+
// cast from *compatible* storage type to extension
61+
let storage_scalar_u64 = Scalar::new(
62+
DType::clone(apples.storage_dtype()),
63+
ScalarValue(InnerScalarValue::Primitive(PValue::U64(1000))),
64+
);
65+
let expected_dtype = &ext_dtype;
66+
let actual = storage_scalar_u64.cast(expected_dtype).unwrap();
67+
assert_eq!(actual.dtype(), expected_dtype);
68+
69+
// cast from *incompatible* storage type to extension
70+
let apples_u8 = ExtDType::new(
71+
ExtID::new(Arc::from("apples")),
72+
Arc::from(DType::Primitive(PType::U8, Nullability::NonNullable)),
73+
None,
74+
);
75+
let expected_dtype = &DType::Extension(Arc::from(apples_u8));
76+
let result = storage_scalar.cast(expected_dtype);
77+
assert!(
78+
result
79+
.as_ref()
80+
.is_err_and(|err| { err.to_string().contains("Cannot cast u16 to u8") }),
81+
"{result:?}"
82+
);
83+
}
84+
85+
#[test]
86+
fn test_f16_coercion_from_u64() {
87+
let f16_value = f16::from_f32(5.722046e-6);
88+
let u64_bits = f16_value.to_bits() as u64;
89+
90+
let scalar = Scalar::new(
91+
DType::Primitive(PType::F16, Nullability::NonNullable),
92+
ScalarValue(InnerScalarValue::Primitive(PValue::U64(u64_bits))),
93+
);
94+
95+
assert_eq!(
96+
scalar.as_primitive().pvalue().unwrap(),
97+
PValue::F16(f16_value)
98+
);
99+
}
100+
101+
#[test]
102+
fn test_f16_coercion_from_u32() {
103+
let f16_value = f16::from_f32(0.42);
104+
let u32_bits = f16_value.to_bits() as u32;
105+
106+
let scalar = Scalar::new(
107+
DType::Primitive(PType::F16, Nullability::NonNullable),
108+
ScalarValue(InnerScalarValue::Primitive(PValue::U32(u32_bits))),
109+
);
110+
111+
assert_eq!(
112+
scalar.as_primitive().pvalue().unwrap(),
113+
PValue::F16(f16_value)
114+
);
115+
}
116+
117+
#[test]
118+
fn test_f16_coercion_from_u16() {
119+
let f16_value = f16::from_f32(1.5);
120+
let u16_bits = f16_value.to_bits();
121+
122+
let scalar = Scalar::new(
123+
DType::Primitive(PType::F16, Nullability::NonNullable),
124+
ScalarValue(InnerScalarValue::Primitive(PValue::U16(u16_bits))),
125+
);
126+
127+
assert_eq!(
128+
scalar.as_primitive().pvalue().unwrap(),
129+
PValue::F16(f16_value)
130+
);
131+
}
132+
133+
#[test]
134+
fn test_f32_coercion_from_u32() {
135+
let f32_value = std::f32::consts::PI;
136+
let u32_bits = f32_value.to_bits();
137+
138+
let scalar = Scalar::new(
139+
DType::Primitive(PType::F32, Nullability::NonNullable),
140+
ScalarValue(InnerScalarValue::Primitive(PValue::U32(u32_bits))),
141+
);
142+
143+
assert_eq!(
144+
scalar.as_primitive().pvalue().unwrap(),
145+
PValue::F32(f32_value)
146+
);
147+
}
148+
149+
#[test]
150+
fn test_f64_coercion_from_u64() {
151+
let f64_value = std::f64::consts::E;
152+
let u64_bits = f64_value.to_bits();
153+
154+
let scalar = Scalar::new(
155+
DType::Primitive(PType::F64, Nullability::NonNullable),
156+
ScalarValue(InnerScalarValue::Primitive(PValue::U64(u64_bits))),
157+
);
158+
159+
assert_eq!(
160+
scalar.as_primitive().pvalue().unwrap(),
161+
PValue::F64(f64_value)
162+
);
163+
}
164+
165+
#[test]
166+
fn test_struct_field_coercion() {
167+
let f16_value = f16::from_f32(0.42);
168+
let f32_value = std::f32::consts::PI;
169+
170+
let struct_dtype = DType::Struct(
171+
StructFields::from_iter([
172+
(
173+
"a",
174+
FieldDType::from(DType::Primitive(PType::U32, Nullability::NonNullable)),
175+
),
176+
(
177+
"b",
178+
FieldDType::from(DType::Primitive(PType::F16, Nullability::NonNullable)),
179+
),
180+
(
181+
"c",
182+
FieldDType::from(DType::Primitive(PType::F32, Nullability::NonNullable)),
183+
),
184+
]),
185+
Nullability::NonNullable,
186+
);
187+
188+
let field_values = vec![
189+
ScalarValue(InnerScalarValue::Primitive(PValue::U32(42))),
190+
ScalarValue(InnerScalarValue::Primitive(PValue::U64(
191+
f16_value.to_bits() as u64,
192+
))),
193+
ScalarValue(InnerScalarValue::Primitive(PValue::F32(f32_value))),
194+
];
195+
196+
let scalar = Scalar::new(
197+
struct_dtype,
198+
ScalarValue(InnerScalarValue::List(field_values.into())),
199+
);
200+
201+
let struct_scalar = scalar.as_struct();
202+
let fields = struct_scalar.fields().unwrap();
203+
204+
// Check first field (no coercion needed)
205+
assert_eq!(fields[0].as_primitive().pvalue().unwrap(), PValue::U32(42));
206+
207+
// Check second field (f16 coerced from u64)
208+
assert_eq!(
209+
fields[1].as_primitive().pvalue().unwrap(),
210+
PValue::F16(f16_value)
211+
);
212+
213+
// Check third field (no coercion needed)
214+
assert_eq!(
215+
fields[2].as_primitive().pvalue().unwrap(),
216+
PValue::F32(f32_value)
217+
);
218+
}
219+
220+
#[test]
221+
fn test_fake_coercion_for_matching_type() {
222+
// Test that when types already match, no coercion happens
223+
let i32_value = 42i32;
224+
let scalar = Scalar::new(
225+
DType::Primitive(PType::I32, Nullability::NonNullable),
226+
ScalarValue(InnerScalarValue::Primitive(PValue::I32(i32_value))),
227+
);
228+
229+
assert_eq!(
230+
scalar.as_primitive().pvalue().unwrap(),
231+
PValue::I32(i32_value)
232+
);
233+
}
234+
235+
#[test]
236+
fn test_list_element_coercion() {
237+
let f16_value1 = f16::from_f32(1.0);
238+
let f16_value2 = f16::from_f32(2.0);
239+
240+
let list_dtype = DType::List(
241+
Arc::new(DType::Primitive(PType::F16, Nullability::NonNullable)),
242+
Nullability::NonNullable,
243+
);
244+
245+
let elements = vec![
246+
ScalarValue(InnerScalarValue::Primitive(PValue::U64(
247+
f16_value1.to_bits() as u64,
248+
))),
249+
ScalarValue(InnerScalarValue::Primitive(PValue::U64(
250+
f16_value2.to_bits() as u64,
251+
))),
252+
];
253+
254+
let scalar = Scalar::new(
255+
list_dtype,
256+
ScalarValue(InnerScalarValue::List(elements.into())),
257+
);
258+
259+
let list_scalar = scalar.as_list();
260+
let elements = list_scalar.elements().unwrap();
261+
262+
for (i, expected) in [f16_value1, f16_value2].iter().enumerate() {
263+
assert_eq!(
264+
elements[i].as_primitive().pvalue().unwrap(),
265+
PValue::F16(*expected)
266+
);
267+
}
268+
}
269+
270+
// TODO(robert): This test should panic (it is storing a garbage value).
271+
#[test]
272+
// #[should_panic]
273+
fn test_coercion_with_overflow_protection() {
274+
// Test that values too large for target type are not coerced
275+
let large_u64 = u64::MAX;
276+
277+
// This should NOT be coerced to F16 because it's too large
278+
let scalar = Scalar::new(
279+
DType::Primitive(PType::F16, Nullability::NonNullable),
280+
ScalarValue(InnerScalarValue::Primitive(PValue::U64(large_u64))),
281+
);
282+
283+
let _ = scalar.as_primitive(); // Should panic
284+
}
285+
286+
#[test]
287+
fn test_extension_dtype_coercion() {
288+
// Create an extension type with f16 storage
289+
let ext_id = ExtID::new("test_f16_ext".into());
290+
let storage_dtype = Arc::new(DType::Primitive(PType::F16, Nullability::NonNullable));
291+
let ext_dtype = Arc::new(ExtDType::new(ext_id, storage_dtype, None));
292+
293+
// Test f16 value stored as u64 gets coerced through extension type
294+
let f16_value = f16::from_f32(0.42);
295+
let u64_bits = f16_value.to_bits() as u64;
296+
297+
let scalar = Scalar::new(
298+
DType::Extension(ext_dtype),
299+
ScalarValue(InnerScalarValue::Primitive(PValue::U64(u64_bits))),
300+
);
301+
302+
// Verify the value was coerced to f16
303+
assert_eq!(
304+
scalar
305+
.as_extension()
306+
.storage()
307+
.as_primitive()
308+
.pvalue()
309+
.unwrap(),
310+
PValue::F16(f16_value)
311+
);
312+
}
313+
314+
#[test]
315+
fn test_extension_dtype_nested_struct_coercion() {
316+
// Create an extension type with struct storage that contains f16 field
317+
let ext_id = ExtID::new("test_struct_ext".into());
318+
let struct_dtype = Arc::new(DType::Struct(
319+
StructFields::from_iter([
320+
(
321+
"id",
322+
FieldDType::from(DType::Primitive(PType::U32, Nullability::NonNullable)),
323+
),
324+
(
325+
"value",
326+
FieldDType::from(DType::Primitive(PType::F16, Nullability::NonNullable)),
327+
),
328+
]),
329+
Nullability::NonNullable,
330+
));
331+
let ext_dtype = Arc::new(ExtDType::new(ext_id, struct_dtype, None));
332+
333+
// Create struct value with f16 stored as u64
334+
let f16_value = f16::from_f32(1.5);
335+
let field_values = vec![
336+
ScalarValue(InnerScalarValue::Primitive(PValue::U32(123))),
337+
ScalarValue(InnerScalarValue::Primitive(PValue::U64(
338+
f16_value.to_bits() as u64,
339+
))),
340+
];
341+
342+
let scalar = Scalar::new(
343+
DType::Extension(ext_dtype),
344+
ScalarValue(InnerScalarValue::List(field_values.into())),
345+
);
346+
347+
// Verify the struct field was coerced
348+
let list_elems = scalar
349+
.as_extension()
350+
.storage()
351+
.as_struct()
352+
.fields()
353+
.vortex_expect("non null");
354+
assert_eq!(
355+
list_elems[0].as_primitive().pvalue().unwrap(),
356+
PValue::U32(123)
357+
);
358+
assert_eq!(
359+
list_elems[1].as_primitive().pvalue().unwrap(),
360+
PValue::F16(f16_value)
361+
);
362+
}
363+
}

0 commit comments

Comments
 (0)