This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-11630-a8306d68e57527a6b929a15cf046bd8550e995ea in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
commit 8a9f96070f405e5a5f85a63cdc4a3d9c32a20086 Author: Spicy Shark <[email protected]> AuthorDate: Sun Oct 18 02:25:00 2026 +0000 fix(arrow-schema): keep map keys-sorted flag in FFI field export (#11630) # Which issue does this PR close? - Closes #11603 # Rationale for this change Exporting a sorted map as a `Field` drops the keys-sorted flag. With `DataType::Map(entries, true)`, `FFI_ArrowSchema::try_from(&DataType)` sets `ARROW_FLAG_MAP_KEYS_SORTED`, but `FFI_ArrowSchema::try_from(&Field)` replaces it with only the nullable and dictionary-ordered flags, so `Field::try_from(&c_schema)` returns `Map(.., false)`. Struct, list, union and `Schema` children are exported as fields, so a sorted map inside any of them is imported as unsorted as well. Exporting the bare `DataType` already round-trips. # What changes are included in this PR? `TryFrom<&Field> for FFI_ArrowSchema` now sets `MAP_KEYS_SORTED` when the field's data type is `DataType::Map(_, true)`, the same way it sets the nullable and dictionary-ordered flags. `FFI_ArrowSchema::with_flags` is unchanged. # Are these changes tested? Yes, two unit tests in `arrow-schema/src/ffi.rs`, using the existing round-trip helpers. Both fail on `main` and pass with this change: - `test_map_keys_sorted_field`: a map field with sorted and unsorted keys, nullable and not, keeps the keys-sorted and nullable flags and its data type through a round trip. - `test_map_keys_sorted_nested`: sorted and unsorted maps round-trip as children of a struct, a `List`, a dense union and a `Schema`. # Are there any significant user-facing changes? No. --- arrow-schema/src/ffi.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/arrow-schema/src/ffi.rs b/arrow-schema/src/ffi.rs index 85fb327285..823a42086a 100644 --- a/arrow-schema/src/ffi.rs +++ b/arrow-schema/src/ffi.rs @@ -889,6 +889,10 @@ impl TryFrom<&Field> for FFI_ArrowSchema { flags |= Flags::DICTIONARY_ORDERED; } + if let DataType::Map(_, true) = field.data_type() { + flags |= Flags::MAP_KEYS_SORTED; + } + let schema = FFI_ArrowSchema::try_from(field.data_type())? .with_name(field.name())? .with_flags(flags)?; @@ -1068,6 +1072,56 @@ mod tests { assert!(arrow_schema.map_keys_sorted()); } + fn map_type(keys_sorted: bool) -> DataType { + let entries = Field::new( + Field::MAP_ENTRIES_FIELD_DEFAULT_NAME, + DataType::Struct(Fields::from(vec![ + Field::new(Field::MAP_KEY_FIELD_DEFAULT_NAME, DataType::Utf8, false), + Field::new(Field::MAP_VALUE_FIELD_DEFAULT_NAME, DataType::Int32, true), + ])), + false, + ); + DataType::Map(Arc::new(entries), keys_sorted) + } + + #[test] + fn test_map_keys_sorted_field() { + for keys_sorted in [false, true] { + for nullable in [false, true] { + let field = Field::new("m", map_type(keys_sorted), nullable); + let c_schema = FFI_ArrowSchema::try_from(&field).unwrap(); + assert_eq!(c_schema.map_keys_sorted(), keys_sorted); + assert_eq!(c_schema.nullable(), nullable); + round_trip_field(field); + } + } + } + + #[test] + fn test_map_keys_sorted_nested() { + let map = |keys_sorted| Field::new("m", map_type(keys_sorted), true); + + for keys_sorted in [false, true] { + round_trip_type(DataType::Struct(Fields::from(vec![ + Field::new("a", DataType::Int32, true), + map(keys_sorted), + ]))); + round_trip_type(DataType::List(Arc::new(map(keys_sorted)))); + round_trip_type(DataType::Union( + UnionFields::try_new( + vec![0, 1], + vec![Field::new("i", DataType::Int32, true), map(keys_sorted)], + ) + .unwrap(), + UnionMode::Dense, + )); + round_trip_schema(Schema::new(vec![ + Field::new("id", DataType::Int64, false), + map(keys_sorted), + ])); + } + } + #[test] fn test_dictionary_ordered() { #[expect(deprecated)]
