vortex_array/arrays/list/compute/
cast.rs1use vortex_error::VortexResult;
5
6use crate::ArrayRef;
7use crate::ExecutionCtx;
8use crate::IntoArray;
9use crate::array::ArrayView;
10use crate::arrays::List;
11use crate::arrays::ListArray;
12use crate::arrays::list::ListArraySlotsExt;
13use crate::builtins::ArrayBuiltins;
14use crate::dtype::DType;
15use crate::scalar_fn::fns::cast::CastKernel;
16use crate::scalar_fn::fns::cast::CastReduce;
17
18impl CastReduce for List {
19 fn cast(array: ArrayView<'_, List>, dtype: &DType) -> VortexResult<Option<ArrayRef>> {
20 let Some(target_element_type) = dtype.as_list_element_opt() else {
21 return Ok(None);
22 };
23
24 let Some(validity) = array
25 .validity()?
26 .trivially_cast_nullability(dtype.nullability(), array.len())?
27 else {
28 return Ok(None);
29 };
30
31 let new_elements = array.elements().cast((**target_element_type).clone())?;
32
33 Ok(Some(
34 unsafe { ListArray::new_unchecked(new_elements, array.offsets().clone(), validity) }
35 .into_array(),
36 ))
37 }
38}
39
40impl CastKernel for List {
41 fn cast(
42 array: ArrayView<'_, List>,
43 dtype: &DType,
44 ctx: &mut ExecutionCtx,
45 ) -> VortexResult<Option<ArrayRef>> {
46 let Some(target_element_type) = dtype.as_list_element_opt() else {
47 return Ok(None);
48 };
49
50 let validity = array
51 .validity()?
52 .cast_nullability(dtype.nullability(), array.len(), ctx)?;
53
54 let new_elements = array.elements().cast((**target_element_type).clone())?;
55
56 Ok(Some(
57 unsafe { ListArray::new_unchecked(new_elements, array.offsets().clone(), validity) }
58 .into_array(),
59 ))
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use std::sync::Arc;
66 use std::sync::LazyLock;
67
68 use rstest::rstest;
69 use vortex_buffer::buffer;
70 use vortex_session::VortexSession;
71
72 use crate::Canonical;
73 use crate::IntoArray;
74 use crate::RecursiveCanonical;
75 use crate::VortexSessionExecute;
76 use crate::arrays::BoolArray;
77 use crate::arrays::ListArray;
78 use crate::arrays::PrimitiveArray;
79 use crate::arrays::VarBinArray;
80 use crate::assert_arrays_eq;
81 use crate::builtins::ArrayBuiltins;
82 use crate::compute::conformance::cast::test_cast_conformance;
83 use crate::dtype::DType;
84 use crate::dtype::Nullability;
85 use crate::dtype::PType;
86 use crate::validity::Validity;
87
88 static SESSION: LazyLock<VortexSession> = LazyLock::new(crate::array_session);
89
90 #[test]
91 fn test_cast_list_success() {
92 let mut ctx = SESSION.create_execution_ctx();
93 let list = ListArray::try_new(
94 buffer![1i32, 2, 3, 4].into_array(),
95 buffer![0, 2, 3].into_array(),
96 Validity::NonNullable,
97 &mut ctx,
98 )
99 .unwrap();
100
101 let target_dtype = DType::List(
102 Arc::new(DType::Primitive(PType::U64, Nullability::Nullable)),
103 Nullability::Nullable,
104 );
105
106 let result = list
107 .clone()
108 .into_array()
109 .cast(target_dtype.clone())
110 .unwrap();
111 assert_eq!(result.dtype(), &target_dtype);
112 assert_eq!(result.len(), list.len());
113 let expected = ListArray::new(
114 PrimitiveArray::from_option_iter([Some(1u64), Some(2), Some(3)]).into_array(),
115 buffer![0u32, 2, 3].into_array(),
116 Validity::AllValid,
117 &mut ctx,
118 );
119 assert_arrays_eq!(result, expected, &mut SESSION.create_execution_ctx());
120 }
121
122 #[test]
123 fn test_cast_to_wrong_type() {
124 let mut ctx = SESSION.create_execution_ctx();
125 let list = ListArray::try_new(
126 buffer![0i32, 2, 3, 4].into_array(),
127 buffer![0, 2, 3].into_array(),
128 Validity::NonNullable,
129 &mut ctx,
130 )
131 .unwrap();
132
133 let target_dtype = DType::Primitive(PType::U64, Nullability::NonNullable);
134 let result = list
137 .into_array()
138 .cast(target_dtype)
139 .and_then(|a| a.execute::<Canonical>(&mut SESSION.create_execution_ctx()))
140 .map(|c| c.into_array());
141 assert!(result.is_err());
142 }
143
144 #[test]
145 fn test_cant_cast_nulls_to_non_null() {
146 let mut ctx = SESSION.create_execution_ctx();
147 let list = ListArray::try_new(
151 buffer![0i32, 2, 3, 4].into_array(),
152 buffer![0, 2, 3].into_array(),
153 Validity::Array(BoolArray::from_iter(vec![false, true]).into_array()),
154 &mut ctx,
155 )
156 .unwrap();
157
158 let target_dtype = DType::List(
159 Arc::new(DType::Primitive(PType::U64, Nullability::Nullable)),
160 Nullability::NonNullable,
161 );
162
163 let result = list
164 .into_array()
165 .cast(target_dtype)
166 .and_then(|a| a.execute::<Canonical>(&mut SESSION.create_execution_ctx()))
167 .map(|c| c.into_array());
168 assert!(result.is_err());
169
170 let list = ListArray::try_new(
173 PrimitiveArray::from_option_iter([Some(0i32), Some(2), None, None]).into_array(),
174 buffer![0, 2, 3].into_array(),
175 Validity::NonNullable,
176 &mut ctx,
177 )
178 .unwrap();
179
180 let target_dtype = DType::List(
181 Arc::new(DType::Primitive(PType::U64, Nullability::NonNullable)),
182 Nullability::NonNullable,
183 );
184
185 let result = list.into_array().cast(target_dtype).and_then(|a| {
186 a.execute::<RecursiveCanonical>(&mut SESSION.create_execution_ctx())
187 .map(|c| c.0.into_array())
188 });
189 assert!(result.is_err());
190 }
191
192 #[rstest]
193 #[case(create_simple_list())]
194 #[case(create_nullable_list())]
195 #[case(create_string_list())]
196 #[case(create_nested_list())]
197 #[case(create_empty_lists())]
198 fn test_cast_list_conformance(#[case] array: ListArray) {
199 test_cast_conformance(&array.into_array(), &mut SESSION.create_execution_ctx());
200 }
201
202 fn create_simple_list() -> ListArray {
203 let mut ctx = SESSION.create_execution_ctx();
204 let data = buffer![1i32, 2, 3, 4, 5, 6].into_array();
205 let offsets = buffer![0i64, 2, 2, 5, 6].into_array();
206
207 ListArray::try_new(data, offsets, Validity::NonNullable, &mut ctx).unwrap()
208 }
209
210 fn create_nullable_list() -> ListArray {
211 let mut ctx = SESSION.create_execution_ctx();
212 let data = PrimitiveArray::from_option_iter([
213 Some(10i64),
214 None,
215 Some(20),
216 Some(30),
217 None,
218 Some(40),
219 ])
220 .into_array();
221 let offsets = buffer![0i64, 3, 6].into_array();
222 let validity = Validity::Array(BoolArray::from_iter(vec![true, false]).into_array());
223
224 ListArray::try_new(data, offsets, validity, &mut ctx).unwrap()
225 }
226
227 fn create_string_list() -> ListArray {
228 let mut ctx = SESSION.create_execution_ctx();
229 let data = VarBinArray::from_iter(
230 vec![Some("hello"), Some("world"), Some("foo"), Some("bar")],
231 DType::Utf8(Nullability::NonNullable),
232 )
233 .into_array();
234 let offsets = buffer![0i64, 2, 4].into_array();
235
236 ListArray::try_new(data, offsets, Validity::NonNullable, &mut ctx).unwrap()
237 }
238
239 fn create_nested_list() -> ListArray {
240 let mut ctx = SESSION.create_execution_ctx();
241 let inner_data = buffer![1i32, 2, 3, 4, 5, 6].into_array();
243 let inner_offsets = buffer![0i64, 2, 3, 6].into_array();
244 let inner_list =
245 ListArray::try_new(inner_data, inner_offsets, Validity::NonNullable, &mut ctx)
246 .unwrap()
247 .into_array();
248
249 let outer_offsets = buffer![0i64, 2, 3].into_array();
251
252 ListArray::try_new(inner_list, outer_offsets, Validity::NonNullable, &mut ctx).unwrap()
253 }
254
255 fn create_empty_lists() -> ListArray {
256 let mut ctx = SESSION.create_execution_ctx();
257 let data = buffer![42u8].into_array();
258 let offsets = buffer![0i64, 0, 0, 1].into_array();
259
260 ListArray::try_new(data, offsets, Validity::NonNullable, &mut ctx).unwrap()
261 }
262}