diff --git a/arrow-row/src/lib.rs b/arrow-row/src/lib.rs index f1cb42d3d8e6..b647dab28894 100644 --- a/arrow-row/src/lib.rs +++ b/arrow-row/src/lib.rs @@ -6169,6 +6169,61 @@ mod tests { assert_eq!(&list, &back[0]); } + /// Ensure dictionaries nested within FixedSizeLists are not flattened + #[test] + fn test_fixed_size_list_of_dictionaries_round_trips() { + // Build one row = ["a", "b"] as + // `FixedSizeList, 2>`. + let dict_dt = DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)); + let element_field = Arc::new(Field::new("item", dict_dt.clone(), true)); + let fsl_dt = DataType::FixedSizeList(Arc::clone(&element_field), 2); + + let values = Arc::new(StringArray::from(vec!["a", "b"])); + let keys = Int32Array::from(vec![0, 1]); + let dict = DictionaryArray::::try_new(keys, values).unwrap(); + let fsl: ArrayRef = Arc::new(FixedSizeListArray::new( + Arc::clone(&element_field), + 2, + Arc::new(dict), + None, + )); + + assert!(RowConverter::supports_fields(&[SortField::new( + fsl_dt.clone() + )])); + + let converter = RowConverter::new(vec![SortField::new(fsl_dt.clone())]).unwrap(); + let rows = converter.convert_columns(&[Arc::clone(&fsl)]).unwrap(); + + // Before the fix this panicked at the `.unwrap()` because + // `convert_rows` returned `Err(InvalidArgumentError(...))`. + let back = converter.convert_rows(&rows).unwrap(); + assert_eq!(back.len(), 1); + + // The returned array is a `FixedSizeList` with a decoded + // (flattened) child — same self-consistent shape the other + // list-like decoders produce for dictionary children. + let out = back[0] + .as_any() + .downcast_ref::() + .expect("decoded array must be a FixedSizeListArray"); + assert_eq!(out.len(), 1); + assert_eq!(out.value_length(), 2); + // Child data type is the flattened `Utf8`, not the declared + // `Dictionary`. Callers that want the dictionary back need to + // re-encode (see the module docs). + assert_eq!(out.values().data_type(), &DataType::Utf8); + + // Sanity: values survived the round trip. + let values = out + .values() + .as_any() + .downcast_ref::() + .expect("child must be a StringArray after flattening"); + assert_eq!(values.value(0), "a"); + assert_eq!(values.value(1), "b"); + } + // Test List with various combinations of nulls and empty lists #[test] fn test_list_null_variations() { diff --git a/arrow-row/src/list.rs b/arrow-row/src/list.rs index c35e614a9fe0..751ecc12ad46 100644 --- a/arrow-row/src/list.rs +++ b/arrow-row/src/list.rs @@ -421,8 +421,17 @@ pub unsafe fn decode_fixed_size_list( let mut children = unsafe { converter.convert_raw(&mut child_rows, validate_utf8) }?; assert_eq!(children.len(), 1); + // Since RowConverter flattens certain data types (i.e. Dictionary), + // we need to use updated data type instead of original field + let corrected_element_field = Arc::new( + element_field + .as_ref() + .clone() + .with_data_type(children[0].data_type().clone()), + ); + FixedSizeListArray::try_new_with_length( - Arc::clone(element_field), + corrected_element_field, *size, children.pop().unwrap(), nulls,