Skip to content

[branch-56] fix: reject a multi-key null-aware join without a NOT IN value key count (backport #27111) - #27176

Merged
mbutrovich merged 1 commit into
apache:branch-56from
alamb:alamb/backport-27111
Nov 6, 2026
Merged

mbutrovich merged 1 commit into
apache:branch-56from
alamb:alamb/backport-27111

Conversation

@alamb

@alamb alamb commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

…unt (apache#27111)

- Closes apache#26569.

leading keys that are `NOT IN` value keys (the rest are correlation
scope keys), and defaulted it to `1` in `HashJoinExecBuilder::new`,
`Join::try_new` and the `LogicalPlanBuilder` join methods. A caller that
builds `(a, b) NOT IN (SELECT x, y ...)` itself and only sets
`with_null_aware(true)` (for example an external adaptive planner)
silently gets one value key, with `b` read as a scope key. The outer row
`(1, NULL)` against the subquery row `(1, 5)` is then kept, although its
`NOT IN` is UNKNOWN and it must be dropped. 55.x rejected such a plan
("null_aware anti join only supports single column join key").

DataFusion's own planning always sets the count, so SQL is not affected.

- `null_aware_value_keys` stays a `usize`, and `0` now means unset. It
is the new default of `Join::try_new`, the `LogicalPlanBuilder` join
methods, `HashJoinExecBuilder::new` and `HashJoinExec::try_new`.
- An unset count is accepted only for a single-key null-aware join,
which is a scalar `NOT IN`. A null-aware join with more keys and no
count fails to build, with an error asking for
`with_null_aware_value_keys`. There is deliberately no "all keys are
value keys" default: that is just as wrong for a correlated subquery.
- The join type, partition mode and an empty `on` are checked before the
count, so a join that no count can fix reports its own error.
- Decorrelation sets the count only on a null-aware join, so every other
join keeps it unset, as `Join::try_new` leaves it.
- The logical and physical proto decoders drop their `.max(1)`, so an
unset count round-trips as unset. A payload predating the field (55.x,
which only allowed single-key null-aware joins) decodes it as `0` and
still reads as a scalar `NOT IN`. The proto field types are unchanged;
only their comments are updated.
- The 56.0.0 upgrade guide section is shortened.

- `test_null_aware_validation_value_keys` (`hash_join/exec.rs`): a
two-key null-aware join with an unset or out-of-range count fails to
build, `HashJoinExec::try_new` rejects a two-key null-aware join, a
single-key join with an unset count builds as a scalar `NOT IN`, and a
join that no count can fix (a two-key `Inner`, a `Partitioned`
`RightAnti` or `LeftAnti`, an empty `on`) reports its own error.
- `null_aware_value_keys_round_trip` (physical proto): an unset count is
rejected on decode with two keys and kept unset with one.
- `decorrelated_mark_join_sets_value_keys_only_when_null_aware` (core
`sql/joins.rs`): scalar and multi-column `IN` mark joins on non-nullable
keys keep the count unset, and their null-aware counterparts carry `1`
and `2`.
- Existing physical tests that built correlated scalar `NOT IN` joins
through `HashJoinExec::try_new` and relied on the old default now state
the count through a test helper.
- Reverting each fix makes the matching test fail: the unset-multi-key
check, the check order, the decoders' `.max(1)` and the decorrelation
guard.

Yes, for code that builds null-aware joins directly: a multi-key
null-aware join must set `with_null_aware_value_keys`; it no longer
silently defaults to one value key. A struct literal that sets the field
to `1` for a non-null-aware join still works, but `0` is now the
default. The field is new in 56, and the upgrade guide is updated.

This pull request and its description were written by Isaac.

---------

Co-authored-by: Isaac <no-reply@databricks.com>
(cherry picked from commit 22aedf0)
@github-actions github-actions Bot added documentation Improvements or additions to documentation logical-expr Logical plan and expressions optimizer Optimizer rules core Core DataFusion crate proto Related to proto crate physical-plan Changes to the physical-plan crate labels Nov 6, 2026
@alamb alamb mentioned this pull request Nov 6, 2026
28 of 53 tasks
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.24460% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.26%. Comparing base (7711800) to head (84f5b49).
⚠️ Report is 1 commits behind head on branch-56.

Files with missing lines Patch % Lines
...tafusion/physical-plan/src/joins/hash_join/exec.rs 93.57% 2 Missing and 5 partials ⚠️
datafusion/proto/src/logical_plan/mod.rs 80.00% 1 Missing ⚠️
Flag Coverage Δ
all-tests 83.26% <94.24%> (+<0.01%) ⬆️
sqllogictests 60.72% <68.57%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

@@            Coverage Diff             @@
##           branch-56   #27176   +/-   ##
==========================================
  Coverage      83.26%   83.26%           
==========================================
  Files           1169     1169           
  Lines         474209   474282   +73     
  Branches      474209   474282   +73     
==========================================
+ Hits          394853   394918   +65     
- Misses         55458    55465    +7     
- Partials       23898    23899    +1     
Files with missing lines Coverage Δ
datafusion/expr/src/logical_plan/builder.rs 86.35% <100.00%> (ø)
datafusion/expr/src/logical_plan/plan.rs 79.29% <100.00%> (ø)
...on/optimizer/src/decorrelate_predicate_subquery.rs 76.44% <100.00%> (+0.02%) ⬆️
datafusion/optimizer/src/eliminate_cross_join.rs 80.00% <100.00%> (ø)
...fusion/physical-plan/src/joins/hash_join/not_in.rs 95.18% <100.00%> (+0.02%) ⬆️
datafusion/proto-models/src/generated/prost.rs 0.00% <ø> (ø)
datafusion/proto/src/logical_plan/mod.rs 59.52% <80.00%> (-0.08%) ⬇️
...tafusion/physical-plan/src/joins/hash_join/exec.rs 90.80% <93.57%> (+0.11%) ⬆️

... and 7 files with indirect coverage changes

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

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

Looks like a clean cherry-pick, approved pending CI. Thanks @alamb!

@mbutrovich
mbutrovich merged commit 86bb0d3 into apache:branch-56 Nov 6, 2026
41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Core DataFusion crate documentation Improvements or additions to documentation logical-expr Logical plan and expressions optimizer Optimizer rules physical-plan Changes to the physical-plan crate proto Related to proto crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants