kosiew opened a new pull request, #17468:
URL: https://github.com/apache/datafusion/pull/17468
## Which issue does this PR close?
* Partial fix for #16579
This is part of a series smaller PRs to replace #17281
---
## Rationale for this change
The existing struct-casting logic was implemented in a narrow context
(NestedSchemaAdapter) and relied on `arrow::compute::cast` for non-struct
casts. That limited reuse and produced surprising failures when the planner
attempted to reconcile mixed or nullable struct fields. This change generalizes
`cast_column` to accept `arrow::compute::CastOptions`, adds a robust
`validate_struct_compatibility` function (which returns `Result<()>` on
success), preserves parent nulls, and integrates the generalized cast function
into `SchemaAdapter` so casting behavior is consistent across the codebase.
By centralizing struct-casting logic we improve maintainability, make
behavior explicit (via `CastOptions`), and fix multiple user-facing failures
where struct fields differed by name, order, or nullability. This change
enables reliable struct-to-struct casts during planning and execution and
simplifies later extension to other adapters.
---
## What changes are included in this PR?
* **API / implementation**
* Generalized `pub fn cast_column(...)` to accept a `&CastOptions`
argument and dispatch to `arrow::compute::cast_with_options` for non-struct
targets.
* Implemented `cast_struct_column(...)` that:
* Validates struct compatibility (`validate_struct_compatibility`)
before attempting to cast children.
* Uses field name lookup rather than positional matching, preserving
order or allowing reordering as required by target schema.
* Fills missing target fields with `new_null_array(...)` of the target
data type.
* Preserves parent-level null bitmap when building the resulting
`StructArray`.
* Propagates contextual error messages when a child field cast fails
(adds field name to context).
* Added `validate_struct_compatibility(...)` returning `Result<()>`
(errors on incompatible types or unsafe nullability changes).
* Introduced and used `DEFAULT_CAST_OPTIONS` in tests and in
`datasource::schema_adapter` to provide a consistent default behavior when
casting in mapping pipelines.
* **Integration**
* Updated `SchemaAdapter`/`SchemaMapping` signatures to accept a
`CastColumnFn` that takes `&CastOptions` and wired the default mapping to call
`cast_column(..., &DEFAULT_CAST_OPTIONS)`.
* Updated callers in `schema_adapter.rs` to validate struct compatibility
and return `Ok(true)`/`Ok(false)` consistently where expected.
* **Tests**
* Added/expanded unit tests for:
* simple primitive casts with options (`test_cast_simple_column`,
`test_cast_column_with_options`)
* struct casts with missing fields and null preservation
(`test_cast_struct_with_missing_field`,
`test_cast_struct_parent_nulls_retained`)
* incompatible child types (`test_cast_struct_incompatible_child_type`)
* nullability compatibility checks
(`test_validate_struct_compatibility_nullable_to_non_nullable`,
`test_validate_struct_compatibility_non_nullable_to_nullable`,
`test_validate_struct_compatibility_nested_nullable_to_non_nullable`)
* nested structs with reordering, extra and missing fields
(`test_cast_nested_struct_with_extra_and_missing_fields`)
* arrays and map fields inside structs
(`test_cast_struct_with_array_and_map_fields`)
* field ordering differences (`test_cast_struct_field_order_differs`)
* **Documentation**
* Added doc comments explaining `CastOptions` usage and included a short
example showing `safe: true` making overflow produce `NULL` rather than an
error.
---
## Are these changes tested?
Yes — the patch adds multiple unit tests in
`datafusion/common/src/nested_struct.rs` covering primitive casts with options,
struct-to-struct casts (including missing/extra fields, null preservation),
nullability validation, nested structs, array/map child fields, and ordering
differences. These tests exercise the new `cast_column` entrypoint and schema
adapter integration.
If CI surfaces any flakiness, follow-ups should add targeted property tests
or fuzzing for deeply nested or strange datatypes.
---
## Are there any user-facing changes?
* Better support for struct-to-struct casting at query-plan and execution
time. Queries that previously failed due to mismatched struct fields or
nullability (for example `select {'a': 1} = {'a': 2, 'b': NULL}`) should behave
more consistently or at least produce clearer errors indicating which child
field failed to cast.
* API changes are limited to internal behavior (the `CastColumnFn` in
`SchemaAdapter` and cast signatures were extended). Public crate API surfaces
remain stable for typical end-users but developers of custom adapters should
update conforming closures to accept `&CastOptions`.
---
## API / Compatibility Notes
* This PR **changes the internal signature** of the cast function used by
`SchemaAdapter` (it now accepts `&CastOptions`). Consumers that construct
`SchemaMapping` by hand in downstream code (or tests) will need to adapt to the
new closure shape.
* No breaking changes to public user-facing Rust API are intended beyond the
internal adapter closure signature; nevertheless, reviewers should confirm
whether `SchemaAdapter` is considered public API in any downstream projects and
coordinate a deprecation or migration path if needed.
---
## Why `Result<bool, _>` was amended to `Result<(), _>`
Originally `validate_struct_compatibility(...)` returned `Result<bool, _>`
where `Ok(true)` meant "compatible" and `Ok(false)` meant "incompatible but
non-error". In practice the function should either succeed (meaning
compatibility checks passed) or fail with a detailed error explaining why the
structs cannot be cast. Returning a `bool` forced callers to interpret a
`false` value and decide whether to convert it into an error. By changing the
return type to `Result<()>` we make the contract clearer and more idiomatic:
* `Ok(())` indicates compatibility.
* `Err(...)` indicates a concrete, actionable reason for incompatibility
(e.g., incompatible child types or unsafe nullability change).
This simplifies callers (they can `?` the validation) and yields richer
error messages for users and better control flow in planning code that needs to
bail on incompatible casts. It avoids a two-step "check then error" pattern and
makes the function safer to use in chained logic.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]