Skip to content

fix: align physical CASE nullability through casts#23844

Merged
adriangb merged 7 commits into
apache:mainfrom
pydantic:friendlymatthew/fix-case-physical-nullability
Jul 24, 2026
Merged

fix: align physical CASE nullability through casts#23844
adriangb merged 7 commits into
apache:mainfrom
pydantic:friendlymatthew/fix-case-physical-nullability

Conversation

@friendlymatthew

Copy link
Copy Markdown
Contributor

Rationale for this change

Logical CASE nullability unwraps null preserving casts before analyzing guarded branches, but physical CASE nullability did not. Type coercion could therefore produce conflicting schemas and cause valid aggregation queries to fail during planning

@github-actions github-actions Bot added physical-expr Changes to the physical-expr crates sqllogictest SQL Logic Tests (.slt) labels Jul 23, 2026
END AS endpoint
FROM foo
)
GROUP BY endpoint

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.

Is there a simpler version of this e.g. without a groupby / aggregation?

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.

This seems to be the simplest repro. At the sql level, a plain projection still executes because nothing consumes the conflicting nullability. The aggregate is what exposes the logical/physical schema mismatch

@adriangb adriangb 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.

Nice fix — mirroring the logical unwrap_certainly_null_expr exactly is the right call, since the failure mode is precisely the logical and physical sides disagreeing. Two small committable suggestions below (minimal repro + a keep-in-sync doc note).

One thing that isn't inline-commentable because the file isn't in this diff: it'd be worth adding the reciprocal note on the logical side too — a // keep in sync with the physical-expr version in datafusion/physical-expr/src/expressions/case.rs on unwrap_certainly_null_expr in datafusion/expr/src/expr_schema.rs — so the contract is visible from both ends.

Optional extra coverage: a case pinning TRY_CAST as intentionally not unwrapped (i.e. WHEN x IS NOT NULL THEN TRY_CAST(x AS ...) stays nullable on both sides) would guard the sync invariant against someone later unwrapping it on only one side; and a nested-wrapper case (e.g. CAST(-x)) would lock in the recursion.

Comment thread datafusion/sqllogictest/test_files/case.slt Outdated
Comment thread datafusion/physical-expr/src/expressions/case.rs
@codecov-commenter

codecov-commenter commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 67.39130% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.71%. Comparing base (1bdff19) to head (49e1b12).
⚠️ Report is 29 commits behind head on main.

Files with missing lines Patch % Lines
datafusion/physical-expr/src/expressions/case.rs 67.39% 1 Missing and 14 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #23844      +/-   ##
==========================================
- Coverage   80.75%   80.71%   -0.04%     
==========================================
  Files        1089     1090       +1     
  Lines      368866   370384    +1518     
  Branches   368866   370384    +1518     
==========================================
+ Hits       297871   298949    +1078     
- Misses      53241    53611     +370     
- Partials    17754    17824      +70     

☔ 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.

Adds unit tests that lock in two properties of unwrap_certainly_null_expr:

- Nested null-preserving wrappers (e.g. CAST(-foo)) are unwrapped
  recursively, so the guarded branch is still proven unreachable-as-null.
- TRY_CAST is intentionally NOT unwrapped: it can yield NULL on a failed
  cast even for a non-null input, so a guarded TRY_CAST branch keeps the
  CASE nullable. This guards the logical/physical sync invariant against
  someone later unwrapping TRY_CAST on only one side.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@adriangb

Copy link
Copy Markdown
Contributor

A couple suggested tests: pydantic#65

test: cover nested wrappers and TRY_CAST in physical CASE nullability
adriangb added 2 commits July 23, 2026 13:31
Co-authored-by: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>
@adriangb
adriangb added this pull request to the merge queue Jul 23, 2026
@adriangb
adriangb removed this pull request from the merge queue due to a manual request Jul 23, 2026
Comment thread datafusion/physical-expr/src/expressions/case.rs Outdated
@adriangb
adriangb enabled auto-merge July 23, 2026 19:58
auto-merge was automatically disabled July 24, 2026 14:38

Head branch was pushed to by a user without write access

@adriangb
adriangb enabled auto-merge July 24, 2026 14:46
@adriangb
adriangb added this pull request to the merge queue Jul 24, 2026
Merged via the queue into apache:main with commit 582453b Jul 24, 2026
40 checks passed
@adriangb
adriangb deleted the friendlymatthew/fix-case-physical-nullability branch July 24, 2026 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

physical-expr Changes to the physical-expr crates sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants