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