1use crate::cast::can_cast_types;
8use crate::cast_with_options;
9use arrow_array::{Array, ArrayRef, UnionArray};
10use arrow_schema::{ArrowError, DataType, FieldRef, UnionFields};
11use arrow_select::union_extract::union_extract_by_id;
12
13use super::CastOptions;
14
15fn same_type_family(a: &DataType, b: &DataType) -> bool {
18 use DataType::*;
19 matches!(
20 (a, b),
21 (Utf8 | LargeUtf8 | Utf8View, Utf8 | LargeUtf8 | Utf8View)
22 | (
23 Binary | LargeBinary | BinaryView,
24 Binary | LargeBinary | BinaryView
25 )
26 | (Int8 | Int16 | Int32 | Int64, Int8 | Int16 | Int32 | Int64)
27 | (
28 UInt8 | UInt16 | UInt32 | UInt64,
29 UInt8 | UInt16 | UInt32 | UInt64
30 )
31 | (Float16 | Float32 | Float64, Float16 | Float32 | Float64)
32 )
33}
34
35pub(crate) fn resolve_child_array<'a>(
52 fields: &'a UnionFields,
53 target_type: &DataType,
54) -> Option<(i8, &'a FieldRef)> {
55 fields
56 .iter()
57 .find(|(_, f)| f.data_type() == target_type)
58 .or_else(|| {
59 fields
60 .iter()
61 .find(|(_, f)| same_type_family(f.data_type(), target_type))
62 })
63 .or_else(|| {
64 if target_type.is_nested() {
68 return None;
69 }
70 fields
71 .iter()
72 .find(|(_, f)| can_cast_types(f.data_type(), target_type))
73 })
74}
75
76pub fn union_extract_by_type(
126 union_array: &UnionArray,
127 target_type: &DataType,
128 cast_options: &CastOptions,
129) -> Result<ArrayRef, ArrowError> {
130 let DataType::Union(fields, _) = union_array.data_type() else {
131 unreachable!("union_extract_by_type called on non-union array")
132 };
133
134 let Some((type_id, _)) = resolve_child_array(fields, target_type) else {
135 return Err(ArrowError::InvalidArgumentError(format!(
136 "cannot cast Union with fields {} to {}",
137 fields
138 .iter()
139 .map(|(_, f)| f.data_type().to_string())
140 .collect::<Vec<_>>()
141 .join(", "),
142 target_type
143 )));
144 };
145
146 let extracted = union_extract_by_id(union_array, type_id)?;
147
148 if extracted.data_type() == target_type {
149 return Ok(extracted);
150 }
151
152 cast_with_options(&extracted, target_type, cast_options)
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use crate::cast;
159 use arrow_array::*;
160 use arrow_schema::{Field, UnionFields, UnionMode};
161 use std::sync::Arc;
162
163 fn int_str_fields() -> UnionFields {
164 UnionFields::try_new(
165 [0, 1],
166 [
167 Field::new("int", DataType::Int32, true),
168 Field::new("str", DataType::Utf8, true),
169 ],
170 )
171 .unwrap()
172 }
173
174 fn int_str_union_type(mode: UnionMode) -> DataType {
175 DataType::Union(int_str_fields(), mode)
176 }
177
178 #[test]
182 fn test_exact_type_match() {
183 let target = DataType::Utf8;
184
185 assert!(can_cast_types(
187 &int_str_union_type(UnionMode::Sparse),
188 &target
189 ));
190
191 let sparse = UnionArray::try_new(
192 int_str_fields(),
193 vec![1_i8, 0, 1].into(),
194 None,
195 vec![
196 Arc::new(Int32Array::from(vec![None, Some(42), None])) as ArrayRef,
197 Arc::new(StringArray::from(vec![Some("hello"), None, Some("world")])),
198 ],
199 )
200 .unwrap();
201
202 let result = cast::cast(&sparse, &target).unwrap();
203 assert_eq!(result.data_type(), &target);
204 let arr = result.as_any().downcast_ref::<StringArray>().unwrap();
205 assert_eq!(arr.value(0), "hello");
206 assert!(arr.is_null(1));
207 assert_eq!(arr.value(2), "world");
208
209 assert!(can_cast_types(
211 &int_str_union_type(UnionMode::Dense),
212 &target
213 ));
214
215 let dense = UnionArray::try_new(
216 int_str_fields(),
217 vec![1_i8, 0, 1].into(),
218 Some(vec![0_i32, 0, 1].into()),
219 vec![
220 Arc::new(Int32Array::from(vec![Some(42)])) as ArrayRef,
221 Arc::new(StringArray::from(vec![Some("hello"), Some("world")])),
222 ],
223 )
224 .unwrap();
225
226 let result = cast::cast(&dense, &target).unwrap();
227 let arr = result.as_any().downcast_ref::<StringArray>().unwrap();
228 assert_eq!(arr.value(0), "hello");
229 assert!(arr.is_null(1));
230 assert_eq!(arr.value(2), "world");
231 }
232
233 #[test]
239 fn test_same_family_utf8_to_utf8view() {
240 let target = DataType::Utf8View;
241
242 assert!(can_cast_types(
244 &int_str_union_type(UnionMode::Sparse),
245 &target
246 ));
247
248 let sparse = UnionArray::try_new(
249 int_str_fields(),
250 vec![1_i8, 0, 1, 1].into(),
251 None,
252 vec![
253 Arc::new(Int32Array::from(vec![None, Some(42), None, None])) as ArrayRef,
254 Arc::new(StringArray::from(vec![
255 Some("agent_alpha"),
256 None,
257 Some("agent_beta"),
258 None,
259 ])),
260 ],
261 )
262 .unwrap();
263
264 let result = cast::cast(&sparse, &target).unwrap();
265 assert_eq!(result.data_type(), &target);
266 let arr = result.as_any().downcast_ref::<StringViewArray>().unwrap();
267 assert_eq!(arr.value(0), "agent_alpha");
268 assert!(arr.is_null(1));
269 assert_eq!(arr.value(2), "agent_beta");
270 assert!(arr.is_null(3));
271
272 assert!(can_cast_types(
274 &int_str_union_type(UnionMode::Dense),
275 &target
276 ));
277
278 let dense = UnionArray::try_new(
279 int_str_fields(),
280 vec![1_i8, 0, 1].into(),
281 Some(vec![0_i32, 0, 1].into()),
282 vec![
283 Arc::new(Int32Array::from(vec![Some(42)])) as ArrayRef,
284 Arc::new(StringArray::from(vec![Some("alpha"), Some("beta")])),
285 ],
286 )
287 .unwrap();
288
289 let result = cast::cast(&dense, &target).unwrap();
290 let arr = result.as_any().downcast_ref::<StringViewArray>().unwrap();
291 assert_eq!(arr.value(0), "alpha");
292 assert!(arr.is_null(1));
293 assert_eq!(arr.value(2), "beta");
294 }
295
296 #[test]
301 fn test_one_directional_cast() {
302 let target = DataType::Boolean;
303
304 assert!(can_cast_types(
306 &int_str_union_type(UnionMode::Sparse),
307 &target
308 ));
309
310 let sparse = UnionArray::try_new(
311 int_str_fields(),
312 vec![0_i8, 1, 0].into(),
313 None,
314 vec![
315 Arc::new(Int32Array::from(vec![Some(42), None, Some(0)])) as ArrayRef,
316 Arc::new(StringArray::from(vec![None, Some("hello"), None])),
317 ],
318 )
319 .unwrap();
320
321 let result = cast::cast(&sparse, &target).unwrap();
322 assert_eq!(result.data_type(), &target);
323 let arr = result.as_any().downcast_ref::<BooleanArray>().unwrap();
324 assert!(arr.value(0));
325 assert!(arr.is_null(1));
326 assert!(!arr.value(2));
327
328 assert!(can_cast_types(
330 &int_str_union_type(UnionMode::Dense),
331 &target
332 ));
333
334 let dense = UnionArray::try_new(
335 int_str_fields(),
336 vec![0_i8, 1, 0].into(),
337 Some(vec![0_i32, 0, 1].into()),
338 vec![
339 Arc::new(Int32Array::from(vec![Some(42), Some(0)])) as ArrayRef,
340 Arc::new(StringArray::from(vec![Some("hello")])),
341 ],
342 )
343 .unwrap();
344
345 let result = cast::cast(&dense, &target).unwrap();
346 let arr = result.as_any().downcast_ref::<BooleanArray>().unwrap();
347 assert!(arr.value(0));
348 assert!(arr.is_null(1));
349 assert!(!arr.value(2));
350 }
351
352 #[test]
356 fn test_duplicate_field_names() {
357 let fields = UnionFields::try_new(
358 [0, 1],
359 [
360 Field::new("val", DataType::Int32, true),
361 Field::new("val", DataType::Utf8, true),
362 ],
363 )
364 .unwrap();
365
366 let target = DataType::Utf8;
367
368 let sparse = UnionArray::try_new(
369 fields.clone(),
370 vec![0_i8, 1, 0, 1].into(),
371 None,
372 vec![
373 Arc::new(Int32Array::from(vec![Some(42), None, Some(99), None])) as ArrayRef,
374 Arc::new(StringArray::from(vec![
375 None,
376 Some("hello"),
377 None,
378 Some("world"),
379 ])),
380 ],
381 )
382 .unwrap();
383
384 let result = cast::cast(&sparse, &target).unwrap();
385 let arr = result.as_any().downcast_ref::<StringArray>().unwrap();
386 assert!(arr.is_null(0));
387 assert_eq!(arr.value(1), "hello");
388 assert!(arr.is_null(2));
389 assert_eq!(arr.value(3), "world");
390
391 let dense = UnionArray::try_new(
392 fields,
393 vec![0_i8, 1, 1].into(),
394 Some(vec![0_i32, 0, 1].into()),
395 vec![
396 Arc::new(Int32Array::from(vec![Some(42)])) as ArrayRef,
397 Arc::new(StringArray::from(vec![Some("hello"), Some("world")])),
398 ],
399 )
400 .unwrap();
401
402 let result = cast::cast(&dense, &target).unwrap();
403 let arr = result.as_any().downcast_ref::<StringArray>().unwrap();
404 assert!(arr.is_null(0));
405 assert_eq!(arr.value(1), "hello");
406 assert_eq!(arr.value(2), "world");
407 }
408
409 #[test]
413 fn test_no_match_errors() {
414 let target = DataType::Struct(vec![Field::new("x", DataType::Int32, true)].into());
415
416 assert!(!can_cast_types(
417 &int_str_union_type(UnionMode::Sparse),
418 &target
419 ));
420
421 let union = UnionArray::try_new(
422 int_str_fields(),
423 vec![0_i8, 1].into(),
424 None,
425 vec![
426 Arc::new(Int32Array::from(vec![Some(42), None])) as ArrayRef,
427 Arc::new(StringArray::from(vec![None, Some("hello")])),
428 ],
429 )
430 .unwrap();
431
432 assert!(cast::cast(&union, &target).is_err());
433 }
434
435 #[test]
439 fn test_exact_match_preferred_over_family() {
440 let fields = UnionFields::try_new(
441 [0, 1],
442 [
443 Field::new("a", DataType::Utf8, true),
444 Field::new("b", DataType::Utf8View, true),
445 ],
446 )
447 .unwrap();
448 let target = DataType::Utf8View;
449
450 assert!(can_cast_types(
451 &DataType::Union(fields.clone(), UnionMode::Sparse),
452 &target,
453 ));
454
455 let union = UnionArray::try_new(
457 fields,
458 vec![0_i8, 1, 0].into(),
459 None,
460 vec![
461 Arc::new(StringArray::from(vec![
462 Some("from_a"),
463 None,
464 Some("also_a"),
465 ])) as ArrayRef,
466 Arc::new(StringViewArray::from(vec![None, Some("from_b"), None])),
467 ],
468 )
469 .unwrap();
470
471 let result = cast::cast(&union, &target).unwrap();
472 assert_eq!(result.data_type(), &target);
473 let arr = result.as_any().downcast_ref::<StringViewArray>().unwrap();
474
475 assert!(arr.is_null(0));
477 assert_eq!(arr.value(1), "from_b");
478 assert!(arr.is_null(2));
479 }
480
481 #[test]
485 fn test_null_in_selected_child_array() {
486 let target = DataType::Utf8;
487
488 assert!(can_cast_types(
489 &int_str_union_type(UnionMode::Sparse),
490 &target
491 ));
492
493 let union = UnionArray::try_new(
496 int_str_fields(),
497 vec![1_i8, 1, 1].into(),
498 None,
499 vec![
500 Arc::new(Int32Array::from(vec![None, None, None])) as ArrayRef,
501 Arc::new(StringArray::from(vec![Some("hello"), None, Some("world")])),
502 ],
503 )
504 .unwrap();
505
506 let result = cast::cast(&union, &target).unwrap();
507 let arr = result.as_any().downcast_ref::<StringArray>().unwrap();
508 assert_eq!(arr.value(0), "hello");
509 assert!(arr.is_null(1));
510 assert_eq!(arr.value(2), "world");
511 }
512
513 #[test]
515 fn test_empty_union() {
516 let target = DataType::Utf8View;
517
518 assert!(can_cast_types(
519 &int_str_union_type(UnionMode::Sparse),
520 &target
521 ));
522
523 let union = UnionArray::try_new(
524 int_str_fields(),
525 Vec::<i8>::new().into(),
526 None,
527 vec![
528 Arc::new(Int32Array::from(Vec::<Option<i32>>::new())) as ArrayRef,
529 Arc::new(StringArray::from(Vec::<Option<&str>>::new())),
530 ],
531 )
532 .unwrap();
533
534 let result = cast::cast(&union, &target).unwrap();
535 assert_eq!(result.data_type(), &target);
536 assert_eq!(result.len(), 0);
537 }
538}