Skip to content

fix(proto): preserve physical cast expression state - #25056

Open
buraksenn wants to merge 8 commits into
apache:mainfrom
buraksenn:24615-cast-proto-destructure
Open

buraksenn wants to merge 8 commits into
apache:mainfrom
buraksenn:24615-cast-proto-destructure

Conversation

@buraksenn

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

Physical cast protobuf hooks did not serialize CastExpr options or explicit target-field metadata. A round trip could therefore change cast behavior, nullability, or metadata. The hooks also accessed fields individually, so future fields could be omitted silently.

What changes are included in this PR?

  • Exhaustively destructure CastExpr, TryCastExpr, and their protobuf messages.
  • Preserve explicit target fields for CAST and TRY_CAST while keeping legacy type-only payload behavior.
  • Preserve all serializable Arrow cast/format options; reject custom formatter factories instead of silently dropping them.
  • Store decoded format strings in owned cast options.
  • Regenerate protobuf models and document the CastExpr::cast_options API adjustment.

The protobuf changes are additive and backward compatible.

What is the testing strategy for this PR?

Added unit coverage for:

  • target-field and cast-option round trips
  • legacy payloads without the new fields
  • explicit default-shaped targets
  • malformed target-field types
  • unsupported formatter factories

Validated with:

  • cargo test -p datafusion-physical-expr --features proto expressions::cast::
  • cargo test -p datafusion-physical-expr --features proto expressions::try_cast::
  • cargo clippy -p datafusion-physical-expr -p datafusion-physical-expr-adapter --all-features --tests -- -D warnings
  • cargo check -p datafusion-proto --all-features
  • ./ci/scripts/doc_prettier_check.sh --write --allow-dirty

Are there any user-facing changes?

Cast expressions now retain their options and explicit output-field semantics across protobuf round trips. CastExpr::cast_options() returns a borrowing CastOptions<'_> value rather than &CastOptions<'static>; the upgrade guide documents migration for code rebuilding casts.

@github-actions github-actions Bot added documentation Improvements or additions to documentation logical-expr Logical plan and expressions physical-expr Changes to the physical-expr crates common Related to common crate labels Sep 8, 2026
@buraksenn
buraksenn marked this pull request as draft September 8, 2026 09:10
@github-actions

github-actions Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

Thank you for opening this pull request!

Reviewer note: cargo-semver-checks reported the current version number is not SemVer-compatible with the changes in this pull request (compared against the base branch).

Details
     Cloning apache/main
    Building datafusion-common v55.1.0 (current)
       Built [  30.110s] (current)
     Parsing datafusion-common v55.1.0 (current)
      Parsed [   0.057s] (current)
    Building datafusion-common v55.1.0 (baseline)
       Built [  29.807s] (baseline)
     Parsing datafusion-common v55.1.0 (baseline)
      Parsed [   0.062s] (baseline)
    Checking datafusion-common v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.834s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  62.408s] datafusion-common
    Building datafusion-expr-common v55.1.0 (current)
       Built [  17.013s] (current)
     Parsing datafusion-expr-common v55.1.0 (current)
      Parsed [   0.016s] (current)
    Building datafusion-expr-common v55.1.0 (baseline)
       Built [  17.326s] (baseline)
     Parsing datafusion-expr-common v55.1.0 (baseline)
      Parsed [   0.017s] (baseline)
    Checking datafusion-expr-common v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.250s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  35.394s] datafusion-expr-common
    Building datafusion-physical-expr v55.1.0 (current)
       Built [  24.532s] (current)
     Parsing datafusion-physical-expr v55.1.0 (current)
      Parsed [   0.045s] (current)
    Building datafusion-physical-expr v55.1.0 (baseline)
       Built [  24.645s] (baseline)
     Parsing datafusion-physical-expr v55.1.0 (baseline)
      Parsed [   0.046s] (baseline)
    Checking datafusion-physical-expr v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.417s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  50.621s] datafusion-physical-expr
    Building datafusion-physical-expr-adapter v55.1.0 (current)
       Built [  27.483s] (current)
     Parsing datafusion-physical-expr-adapter v55.1.0 (current)
      Parsed [   0.006s] (current)
    Building datafusion-physical-expr-adapter v55.1.0 (baseline)
       Built [  27.072s] (baseline)
     Parsing datafusion-physical-expr-adapter v55.1.0 (baseline)
      Parsed [   0.006s] (baseline)
    Checking datafusion-physical-expr-adapter v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.100s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  55.783s] datafusion-physical-expr-adapter
    Building datafusion-proto v55.1.0 (current)
       Built [  47.099s] (current)
     Parsing datafusion-proto v55.1.0 (current)
      Parsed [   0.012s] (current)
    Building datafusion-proto v55.1.0 (baseline)
       Built [  46.634s] (baseline)
     Parsing datafusion-proto v55.1.0 (baseline)
      Parsed [   0.012s] (baseline)
    Checking datafusion-proto v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.132s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  95.247s] datafusion-proto
    Building datafusion-proto-models v55.1.0 (current)
       Built [  21.322s] (current)
     Parsing datafusion-proto-models v55.1.0 (current)
      Parsed [   0.129s] (current)
    Building datafusion-proto-models v55.1.0 (baseline)
       Built [  21.093s] (baseline)
     Parsing datafusion-proto-models v55.1.0 (baseline)
      Parsed [   0.129s] (baseline)
    Checking datafusion-proto-models v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   2.015s] 223 checks: 222 pass, 1 fail, 0 warn, 31 skip

--- failure constructible_struct_adds_field: struct exhaustively constructible through public API adds field ---

Description:
A pub struct that could be exhaustively constructed with a literal using only public API has a new pub field, breaking existing exhaustive literals.
        ref: https://doc.rust-lang.org/reference/expressions/struct-expr.html
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.50.0/src/lints/constructible_struct_adds_field.ron

Failed in:
  field PhysicalCastNode.target_field in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:1952
  field PhysicalCastNode.cast_options in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:1955
  field PhysicalCastNode.target_field in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:1952
  field PhysicalCastNode.cast_options in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:1955
  field PhysicalTryCastNode.target_field in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:1941
  field PhysicalTryCastNode.target_field in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:1941

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [  45.860s] datafusion-proto-models

@github-actions github-actions Bot added the auto detected api change Auto detected API change label Sep 8, 2026
@codecov-commenter

codecov-commenter commented Sep 8, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 55.16373% with 356 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.75%. Comparing base (de06656) to head (0cf2ae4).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
datafusion/proto-models/src/generated/pbjson.rs 0.00% 315 Missing ⚠️
datafusion/physical-expr/src/expressions/cast.rs 92.73% 18 Missing and 7 partials ⚠️
datafusion/proto-models/src/generated/prost.rs 0.00% 11 Missing ⚠️
...tafusion/physical-expr/src/expressions/try_cast.rs 95.76% 2 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #25056      +/-   ##
==========================================
- Coverage   82.80%   82.75%   -0.06%     
==========================================
  Files        1149     1149              
  Lines      452234   452981     +747     
  Branches   452234   452981     +747     
==========================================
+ Hits       374484   374874     +390     
- Misses      54952    55299     +347     
- Partials    22798    22808      +10     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@buraksenn
buraksenn marked this pull request as ready for review September 8, 2026 16:26

@kosiew kosiew left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@buraksenn,

Thanks for working on this. The changes preserve cast options and explicit target-field semantics across protobuf round trips while maintaining compatibility with legacy messages. I found no blocking issues.

I have one optional suggestion for additional behavioral round-trip coverage. This is not a merge blocker.

I reviewed the tests but did not execute them locally.

}

#[test]
fn cast_options_survive_proto_round_trip() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a wire round-trip test in datafusion/proto/tests/cases/plans/ using a ProjectionExec that casts an invalid string like "9.1" from Utf8 to Int32? It would be useful to serialize and deserialize the plan with physical_plan_to_bytes and physical_plan_from_bytes, then verify that both plans return NULL with safe=true and an error with safe=false. The existing test checks option equality but not execution behavior across the public serialization path. This can be a follow-up.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the review I've added the round trip test

@github-actions github-actions Bot added the proto Related to proto crate label Oct 12, 2026
@kosiew

kosiew commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

🚀
@buraksenn
Thank you for your contribution.

@kosiew
kosiew added this pull request to the merge queue Oct 13, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to no response for status checks Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto detected api change Auto detected API change common Related to common crate documentation Improvements or additions to documentation logical-expr Logical plan and expressions physical-expr Changes to the physical-expr crates proto Related to proto crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Destructure proto hooks for cast physical expressions

3 participants