This is an automated email from the ASF dual-hosted git repository.
Jefffrey pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
The following commit(s) were added to refs/heads/main by this push:
new f397a4b4b0 Split arrow_cast::cast struct helpers into their own
submodule (#11074)
f397a4b4b0 is described below
commit f397a4b4b0eda01f149224de95be4b5913efb753
Author: Amit Vijapur <[email protected]>
AuthorDate: Thu Sep 17 02:18:42 2026 +0100
Split arrow_cast::cast struct helpers into their own submodule (#11074)
# Which issue does this PR close?
Part of #11032. Takes the **struct conversion helpers** group; the other
four groups are left for separate PRs as the issue asks.
# Rationale for this change
`arrow-cast/src/cast/mod.rs` is 14,312 lines. #5125 and its follow-ups
(#5537, #5552, #5555, #5563) moved list, decimal, dictionary, string,
map, run-array and union casting into private submodules. The struct
helpers are a small cohesive group still sitting in `mod.rs`, so this
continues that series in the same shape.
# What changes are included in this PR?
- New private module `arrow-cast/src/cast/structs.rs` containing
`cast_struct_to_struct`, `cast_struct_fields_by_name` and
`cast_struct_fields_in_order`, moved from `mod.rs`.
- `mod structs;` and `use crate::cast::structs::*;` added to `mod.rs` in
alphabetical position, matching how the existing submodules are wired.
- `cast_struct_to_struct` becomes `pub(crate)` because
`cast_with_options` calls it from `mod.rs`. The other two are only
called from within the new file and stay plain `fn`, following
`list.rs`, which keeps its inner helper private.
- `cast_with_options` is untouched and remains the dispatcher; the call
site at `mod.rs:1233` is byte-identical to `main`.
The module is named `structs` because `struct` is a keyword. Happy to
rename to `struct_array` or anything else if preferred.
No behaviour change. To verify that mechanically rather than by
inspection: taking the lines removed from `mod.rs` and diffing them
against the body of `structs.rs` with the one `pub(crate)` prefix
stripped, the only difference is the blank line that separated the last
helper from `cast_from_decimal`, which correctly stays in `mod.rs`. The
67 lines of code, including comments and wrapping, are identical.
# Are these changes tested?
By the existing tests. `test_cast_struct_to_struct`,
`test_cast_struct_to_struct_nullability` and the other `cast_struct_*`
tests exercise these helpers through the public `cast` API and are
unchanged.
- `cargo test -p arrow-cast --lib`: 379 passed, 0 failed
- `cargo clippy -p arrow-cast --all-targets --all-features -- -D
warnings`: clean
- `cargo fmt --all -- --check`: clean
- `cargo doc -p arrow-cast --no-deps`: none of the three helper names
appear in the generated docs, confirming nothing new is exported
# Are there any user-facing changes?
No. The three functions were private before and remain crate-private;
`structs` is a private module and the glob import is not `pub use`.
# AI usage disclosure
Per CONTRIBUTING.md: the extraction was performed with Claude Code
assistance. The moved code is the existing upstream code, not generated.
AI was used to locate the helper group, perform the mechanical move, and
run the verification above. I reviewed the full diff and the
verification output before opening this.
Co-authored-by: Andrew Lamb <[email protected]>
---
arrow-cast/src/cast/mod.rs | 70 +---------------------------------
arrow-cast/src/cast/structs.rs | 86 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 88 insertions(+), 68 deletions(-)
diff --git a/arrow-cast/src/cast/mod.rs b/arrow-cast/src/cast/mod.rs
index 6cc0eba829..08441a9930 100644
--- a/arrow-cast/src/cast/mod.rs
+++ b/arrow-cast/src/cast/mod.rs
@@ -43,6 +43,7 @@ mod list;
mod map;
mod run_array;
mod string;
+mod structs;
mod union;
use crate::cast::decimal::*;
@@ -51,6 +52,7 @@ use crate::cast::list::*;
use crate::cast::map::*;
use crate::cast::run_array::*;
use crate::cast::string::*;
+use crate::cast::structs::*;
pub use crate::cast::union::*;
use arrow_buffer::IntervalMonthDayNano;
@@ -2315,74 +2317,6 @@ pub fn cast_with_options(
}
}
-fn cast_struct_to_struct(
- array: &StructArray,
- from_fields: Fields,
- to_fields: Fields,
- cast_options: &CastOptions,
-) -> Result<ArrayRef, ArrowError> {
- // Fast path: if field names are in the same order, we can just zip and
cast
- let fields_match_order = from_fields.len() == to_fields.len()
- && from_fields
- .iter()
- .zip(to_fields.iter())
- .all(|(f1, f2)| f1.name() == f2.name());
-
- let fields = if fields_match_order {
- // Fast path: cast columns in order if their names match
- cast_struct_fields_in_order(array, to_fields.clone(), cast_options)?
- } else {
- let all_fields_match_by_name = to_fields.iter().all(|to_field| {
- from_fields
- .iter()
- .any(|from_field| from_field.name() == to_field.name())
- });
-
- if all_fields_match_by_name {
- // Slow path: match fields by name and reorder
- cast_struct_fields_by_name(array, from_fields.clone(),
to_fields.clone(), cast_options)?
- } else {
- // Fallback: cast field by field in order
- cast_struct_fields_in_order(array, to_fields.clone(),
cast_options)?
- }
- };
-
- let array = StructArray::try_new(to_fields.clone(), fields,
array.nulls().cloned())?;
- Ok(Arc::new(array) as ArrayRef)
-}
-
-fn cast_struct_fields_by_name(
- array: &StructArray,
- from_fields: Fields,
- to_fields: Fields,
- cast_options: &CastOptions,
-) -> Result<Vec<ArrayRef>, ArrowError> {
- to_fields
- .iter()
- .map(|to_field| {
- let from_field_idx = from_fields
- .iter()
- .position(|from_field| from_field.name() == to_field.name())
- .unwrap(); // safe because we checked above
- let column = array.column(from_field_idx);
- cast_with_options(column, to_field.data_type(), cast_options)
- })
- .collect::<Result<Vec<ArrayRef>, ArrowError>>()
-}
-
-fn cast_struct_fields_in_order(
- array: &StructArray,
- to_fields: Fields,
- cast_options: &CastOptions,
-) -> Result<Vec<ArrayRef>, ArrowError> {
- array
- .columns()
- .iter()
- .zip(to_fields.iter())
- .map(|(l, field)| cast_with_options(l, field.data_type(),
cast_options))
- .collect::<Result<Vec<ArrayRef>, ArrowError>>()
-}
-
fn cast_from_decimal<D, F>(
array: &dyn Array,
base: D::Native,
diff --git a/arrow-cast/src/cast/structs.rs b/arrow-cast/src/cast/structs.rs
new file mode 100644
index 0000000000..feb25313ba
--- /dev/null
+++ b/arrow-cast/src/cast/structs.rs
@@ -0,0 +1,86 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use crate::cast::*;
+
+pub(crate) fn cast_struct_to_struct(
+ array: &StructArray,
+ from_fields: Fields,
+ to_fields: Fields,
+ cast_options: &CastOptions,
+) -> Result<ArrayRef, ArrowError> {
+ // Fast path: if field names are in the same order, we can just zip and
cast
+ let fields_match_order = from_fields.len() == to_fields.len()
+ && from_fields
+ .iter()
+ .zip(to_fields.iter())
+ .all(|(f1, f2)| f1.name() == f2.name());
+
+ let fields = if fields_match_order {
+ // Fast path: cast columns in order if their names match
+ cast_struct_fields_in_order(array, to_fields.clone(), cast_options)?
+ } else {
+ let all_fields_match_by_name = to_fields.iter().all(|to_field| {
+ from_fields
+ .iter()
+ .any(|from_field| from_field.name() == to_field.name())
+ });
+
+ if all_fields_match_by_name {
+ // Slow path: match fields by name and reorder
+ cast_struct_fields_by_name(array, from_fields.clone(),
to_fields.clone(), cast_options)?
+ } else {
+ // Fallback: cast field by field in order
+ cast_struct_fields_in_order(array, to_fields.clone(),
cast_options)?
+ }
+ };
+
+ let array = StructArray::try_new(to_fields.clone(), fields,
array.nulls().cloned())?;
+ Ok(Arc::new(array) as ArrayRef)
+}
+
+fn cast_struct_fields_by_name(
+ array: &StructArray,
+ from_fields: Fields,
+ to_fields: Fields,
+ cast_options: &CastOptions,
+) -> Result<Vec<ArrayRef>, ArrowError> {
+ to_fields
+ .iter()
+ .map(|to_field| {
+ let from_field_idx = from_fields
+ .iter()
+ .position(|from_field| from_field.name() == to_field.name())
+ .unwrap(); // safe because we checked above
+ let column = array.column(from_field_idx);
+ cast_with_options(column, to_field.data_type(), cast_options)
+ })
+ .collect::<Result<Vec<ArrayRef>, ArrowError>>()
+}
+
+fn cast_struct_fields_in_order(
+ array: &StructArray,
+ to_fields: Fields,
+ cast_options: &CastOptions,
+) -> Result<Vec<ArrayRef>, ArrowError> {
+ array
+ .columns()
+ .iter()
+ .zip(to_fields.iter())
+ .map(|(l, field)| cast_with_options(l, field.data_type(),
cast_options))
+ .collect::<Result<Vec<ArrayRef>, ArrowError>>()
+}