Skip to content

fix: correct nullability of AND/OR expressions to resolve plan mismatch in CSE - #26163

Closed
siddubakka wants to merge 5 commits into
apache:mainfrom
siddubakka:fix-issue-25978
Closed

siddubakka wants to merge 5 commits into
apache:mainfrom
siddubakka:fix-issue-25978

Conversation

@siddubakka

Copy link
Copy Markdown

This PR fixes an internal error where the physical input schema mismatches the logical input schema due to incorrect nullability propagation for AND/OR expressions in CommonSubexprEliminate.

It adds robust contains_is_not_null and contains_is_null helper functions to accurately determine if binary expressions are nullable.

cc @alamb @jayzhan211

siddubakka and others added 5 commits October 8, 2026 00:51
apache#26106)

## Which issue does this PR close?

- Closes apache#26106.

## Rationale for this change

`JoinSelection` is not safe to run on plans that already carry dynamic
filters, which occurs during re-optimization passes (e.g. downstream
pipelines that wrap an already-optimized plan into a writer sink and
re-run physical optimization).

Once `FilterPushdown` has wired up a dynamic filter between the build
side and probe side of a `HashJoinExec`, swapping the inputs via
`swap_inputs` panics with:
`Internal error: Cannot swap HashJoinExec inputs after dynamic filter has been constructed`

The join's build side is already committed at that stage, and swapping
inputs would invalidate the dynamic filter expressions that reference
probe-side columns. `JoinSelection` should detect this and leave the
`HashJoinExec` unchanged instead of failing the query.

## What changes are included in this PR?

- In `JoinSelection::statistical_join_selection_subrule`, check if
  `!hash_join.dynamic_expressions_produced().is_empty()` and return
  `None` (leaving the plan unchanged).
- In `can_swap_hash_join`, guard against swapping when dynamic expressions
  are produced.
- In `hash_join_swap_subrule`, guard against swapping unbounded left inputs
  when dynamic expressions are produced.
- Added tests in `join_selection.rs` verifying that `JoinSelection` skips
  `HashJoinExec` carrying dynamic filters in both `CollectLeft` and
  `Partitioned` modes.

## What is the testing strategy for this PR?

Added `test_join_selection_skips_hash_join_with_dynamic_filter` in
`datafusion/core/tests/physical_optimizer/join_selection.rs` verifying
that `JoinSelection` leaves the plan unchanged for both `CollectLeft`
and `Partitioned` modes without error.

## Are there any user-facing changes?

No API changes. Fixes an internal error when re-optimizing plans that
contain dynamic filters.
@siddubakka siddubakka closed this Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants