Skip to content

feature: adapt predicate pushdown for mismatched nested/struct schemas #16565

Description

@adriangb

As per discussion in #16461 (comment) we should be able to have datafusion/physical-expr/src/schema_rewriter.rs handle missing sub-fields in struct columns.

Activity

  1. self-assigned this
    on Jun 26, 2025
  2. added theissue type on Jun 26, 2025
  3. alamb commented on Jun 26, 2025

    @alamb
    Contributor

    I think #16371 from @kosiew has the core piece of what is needed here -- namely the struct aware casting kernel

  4. adriangb commented on Jul 4, 2025

    @adriangb
    ContributorAuthor

    @kosiew do you think we could incorporate the struct aware casting you added in #16371 into CastExpr and TryCastExpr?

  5. kosiew commented on Jul 4, 2025

    @kosiew
    Contributor

    @adriangb,

    incorporating the struct-aware casting logic from #16371 into CastExpr and TryCastExpr

    Yes. I think it is a necessary step to explore the feasibility of rewriting Expr for schema adaptation.

    A few thoughts on this:

    1. Reusability of struct-aware casting kernels: The core struct-aware casting kernel introduced in Add nested struct casting support and integrate into SchemaAdapter #16371 was designed to recursively handle casting between nested struct types, dealing with missing or reordered fields. Integrating this logic into CastExpr and TryCastExpr would unify casting semantics across physical expressions, ensuring consistent behavior during predicate rewrite and evaluation.

    2. Improved predicate pushdown: Predicate pushdown often requires projecting predicates down into struct subfields. If schemas differ between scans (e.g., fields missing or reordered), leveraging struct-aware casts would allow predicates to be adapted instead of dropped—thus generating more selective filters pushed down to data sources.

    3. Implementation considerations: We would need to extend CastExpr and TryCastExpr to detect when the input/output types are structs and invoke the struct-aware casting logic appropriately. This means:

      • Adding recursive traversal logic in these expressions.
      • Handling missing subfields by either filling with NULLs or using default values as per the kernel's design.
      • Handling field reordering and type promotion gracefully.
    4. Challenge: Rewriting the struct-aware casting logic from Add nested struct casting support and integrate into SchemaAdapter #16371 into manipulating expressions. Your expression rewrite prowess bends my mind.

  6. adriangb commented on Jul 4, 2025

    @adriangb
    ContributorAuthor

    Question: will we be able to handle something like Dict(UInt32, List(List(Struct(...))))?

  7. alamb commented on Jul 5, 2025

    @alamb
    Contributor

    Implementation considerations: We would need to extend CastExpr and TryCastExpr to detect when the input/output types are structs and invoke the struct-aware casting logic appropriately. This means:

    @kosiew I would actually suggest something different which is to add a function that "wraps" the arrow cast kernel

    So it would look somethin glike

    pub fn cast(
        array: &dyn Array,
        to_type: &DataType,
    ) -> Result<Arc<dyn Array>, ArrowError> {
      if matches!(to_type, DataType::Struct) {
        // call struct aware version in datafusion
       } else {
         // fallback to arrow implementatin
         arrow::compute::kernels::cast(array, to_type)
        }
    }

    Then you need to update all the places that directly call arrow cast with this new cast version, but the upside is that now everything would have the same (consistent) semantics

  8. adriangb commented on Jul 5, 2025

    @adriangb
    ContributorAuthor

    I agree with you @alamb.

    The issue I see with both approaches is going to be nested types: once you call the arrow cast kernel you can't take back control, so this approach won't work for List(Struct) and such. We'd essentially have to handle all non-leaf types in our wrapper and only delegate to arrow for the leaf types that can't possibly contain a struct / other type we want to handle.

  9. alamb commented on Jul 7, 2025

    @alamb
    Contributor

    I agree with you @alamb.

    The issue I see with both approaches is going to be nested types: once you call the arrow cast kernel you can't take back control, so this approach won't work for List(Struct) and such. We'd essentially have to handle all non-leaf types in our wrapper and only delegate to arrow for the leaf types that can't possibly contain a struct / other type we want to handle.

    I think delegating to arrow cast for leaf types makes sense, given its limitations casting nested types

  10. adriangb commented on Mar 27, 2026

    @adriangb
    ContributorAuthor

    @kosiew I think we're good here and you've implemented schema adaptation for struct fields, we can close this issue right? Even List(Struct) works now?

  11. kosiew commented on Mar 30, 2026

    @kosiew
    Contributor

    @adriangb

    Yep, we can close this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions