fix: skip dynamic filter pushdown for null-aware anti joins with a nullable build key - #23173
Conversation
|
@mdashti |
|
@mdashti Can you resolve the CI failures when you get a chance? |
The pushed filter can empty the probe. A null-aware `LeftAnti` then reads it as an empty subquery and emits build-side NULLs that should be dropped. A NOT NULL build key is unaffected.
Indexing on[0] leaned on the single-key invariant without saying so. Iterating all keys survives a future multi-key relaxation. In addition, added allow/reject unit tests to pin both directions of the guard.
The guard removes the probe-side dynamic filter from these plans.
32ead3e to
e512368
Compare
|
@kosiew @neilconway the failures were stale |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #23173 +/- ##
==========================================
- Coverage 80.70% 80.70% -0.01%
==========================================
Files 1089 1089
Lines 368038 368096 +58
Branches 368038 368096 +58
==========================================
+ Hits 297031 297067 +36
- Misses 53308 53325 +17
- Partials 17699 17704 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
run benchmark tpch |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing moe/null-aware-build-null (e512368) to 4184b07 (merge-base) diff using: tpch File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)Details
Resource Usagetpch — base (merge-base)
tpch — branch
File an issue against this benchmark runner |
| return false; | ||
| } | ||
|
|
||
| // A null-aware anti join emits a build-side NULL only when the probe |
There was a problem hiding this comment.
the comment says anto-join but the check isn't for join type. I wonder if that is a discrepancy?
Which issue does this close?
NOT IN+ dynamic filters + build-side NULL returns incorrect results #23126.Rationale for this change
x NOT IN (subquery)plans to a null-awareLeftAntihash join (build = outerx, probe = subquery). Join dynamic filter pushdown pushes a bounds + membership filter, built from the build keys, onto the probe scan. That filter can prune every probe row. A null-awareLeftAntireads an empty probe as a genuinely-empty subquery, so it emits build-side NULL rows that should drop:NULL NOT IN (non-empty)is UNKNOWN, not TRUE.The result is scan-dependent, so it's a silent correctness bug. A
VALUESscan ignores the pushed filter and stays correct; a parquet scan applies it and is wrong.#23103 (the probe-side NULL drop) is orthogonal; this is the build-side NULL.
What changes are included in this PR?
Skip join dynamic filter pushdown for a null-aware anti join when the build key can be NULL. The build-side NULL emission depends on whether the probe is truly empty, which the pushed filter can change by emptying it. A NOT NULL build key has no such NULL, so it keeps the pushdown.
The check is static: a schema-nullable build key disables the pushdown even when the data contains no NULLs. A runtime alternative (keep the pushdown and neutralize the filter only when the build actually holds a NULL key) would restore the optimization for those cases. I'd leave that as a follow-up.
Are these changes tested?
Yes. A
push_down_filter_parquet.sltcase reproduces it (build-side NULL, a non-matching parquet probe) and asserts the single correct row. Without the change it returns the extra NULL. In addition, unit tests pin both directions of the guard: a nullable build key rejects the pushdown and a NOT NULL build key keeps it.Are there any user-facing changes?
NOT INover a parquet (or otherwise prunable) scan with a nullable outer key now returns correct results. Such joins lose the dynamic filter pushdown.