Conversation
A NOT IN subquery plans as a null-aware join, where an outer row that finds no match is TRUE only if neither side has a NULL in scope and UNKNOWN otherwise. Deciding that is cheap for an uncorrelated NOT IN, but a correlated one leaves its correlation predicate behind as a join filter, and the join has to evaluate that filter per candidate (build row x probe row) pair to work out which rows the NULLs actually reach. Without an equality correlation there is no scope key to narrow those pairs, so the cost grows with the NULL count times the opposite table's size. Nothing measured that shape, so add a null_aware_join suite covering it: - Q01-Q03 uncorrelated NOT IN across NULL fractions, linear in the table size, as the regression guard for the plain null-aware path. - Q04 the correlated shape with nullable keys that hold no NULL, so the zero-NULL baseline is separated from the per-pair filter work. - Q05-Q07 the same correlation at 1% and 50% NULL on each side, which is where that work shows up. - Q08 the same NULL fraction as Q06 but with an equality correlation, so the candidate pairs come from a hash lookup instead; the gap between the two is what the scope key buys. All tables are built inline from range(), so there is no data step. Sizes are knobs: NAJ_ROWS for the correlated queries, NAJ_LARGE_ROWS for the rest. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019tS7mXDfq2faC9Xe6EXtZB
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #25386 +/- ##
========================================
Coverage 81.93% 81.93%
========================================
Files 1136 1136
Lines 428859 429152 +293
Branches 428859 429152 +293
========================================
+ Hits 351376 351628 +252
- Misses 56462 56479 +17
- Partials 21021 21045 +24 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Which issue does this PR close?
mainfirst, and that PR can then be measured against it.Rationale for this change
A
NOT INsubquery becomes a null-aware join. An outer row that finds no match is TRUE only when neither side has a NULL in scope. If a NULL is in scope, the result is UNKNOWN.This decision is cheap for an uncorrelated
NOT IN. For a correlatedNOT IN, the correlation predicate stays behind as a join filter. The join must then evaluate that filter for each candidate (build row x probe row) pair, to find which rows the NULLs reach. A non-equality correlation gives no equality key, so there is no scope key to reduce the number of pairs. The cost then grows with the NULL count multiplied by the size of the opposite table.No benchmark measured this shape, so there was no way to see the cost, or to tell a change from noise. Review on #25339 asked for this benchmark.
These are the measured results for #25339. Each number is the median of 60 iterations, taken as 6 interleaved rounds of 10 iterations on an Apple M4 Pro in release mode. The two sides are the base commit of #25339 and its head commit, each with this suite applied, so the comparison isolates the change in that PR.
Q01 to Q04 are the comparable rows, and they show no change. The base gives wrong results for Q05 to Q08, which is the bug that #25339 corrects, so those four rows show the cost of correct results, not a regression.
Q06 and Q07 are the rows that the review of #25339 asked about. They also give the baseline to measure any later optimization of that path against. Q08 has the same NULL fraction as Q06 and is 6 times cheaper, which is the value of the equality correlation.
What changes are included in this PR?
A
null_aware_joinSQL benchmark suite. There are no Rust changes. The runner finds suites inbenchmarks/sql_benchmarks/, and the load SQL makes each table fromrange(), so there is no data generation step.NOT INat different NULL fractions. Their cost is linear with the table size. They are the regression guard for the plain null-aware path.Both table sizes are knobs.
NAJ_ROWS(default 10000) sets the size for the correlated queries, whose cost grows with its square.NAJ_LARGE_ROWS(default 1000000) sets the size for the uncorrelated queries../bench.sh run null_aware_join # One query, with more rows for the correlated shape NAJ_ROWS=20000 ./bench.sh run null_aware_join 6This PR also adds the suite to
bench.shand documents it inbenchmarks/README.mdandbenchmarks/sql_benchmarks/README.md.What is the testing strategy for this PR?
This PR adds benchmarks, so it adds no new tests. The existing
checked_in_suites_cover_benchmark_directoriestest inbenchmarks/src/sql_benchmark_suite.rscovers suite discovery, and it passes with the new directory. All eight queries were run on this branch. Each one assertsHashJoinExecin its plan through theexpect_plandirective.Each query also runs on
mainas written. Q08 uses the mark join form on purpose. The plainWHERE ... NOT INform with an equality correlation does not plan onmain, and a query that runs on only one branch cannot compare two branches.Are there any user-facing changes?
No. This PR changes benchmarks and documentation only. It does not change library code.
🤖 Generated with Claude Code