viirya opened a new pull request, #24746:
URL: https://github.com/apache/datafusion/pull/24746

   ## Which issue does this PR close?
   
   - Closes #24745.
   
   ## Rationale for this change
   
   A memory-limited `NestedLoopJoinExec` silently returned fewer rows than the 
same query with an ample memory pool. No error was raised, so the loss is 
invisible to the caller.
   
   In memory-limited mode the left side is processed in chunks and the right 
side is replayed for each chunk. A per-right-batch match bitmap therefore 
cannot be emitted as soon as one chunk finishes probing it — a later chunk may 
still match those rows. The operator already handles this by merging each 
bitmap into `SpillStateActive::global_right_bitmaps` and deferring emission to 
`NLJState::EmitGlobalRightUnmatched`, which is reached from 
`handle_emit_left_unmatched` once the left side is exhausted.
   
   `handle_buffering_left` had a separate early exit:
   
   ```rust
   if active.pending_batches.is_empty() {
       // No data at all — go directly to Done
       self.left_exhausted = true;
       self.state = NLJState::Done;
       return ControlFlow::Continue(());
   }
   ```
   
   Despite the comment, this also fires on the load that follows the final left 
chunk, when the left side is exhausted and there is nothing more to buffer. 
Ending the stream there discards the accumulated bitmaps, so every probe-side 
row that no chunk matched is dropped.
   
   Instrumenting a failing `LEFT JOIN` showed 39 bitmap merges and zero 
emissions, with the completion path only ever running while `left_exhausted` 
was still `false`.
   
   The affected queries reach the operator as swapped join types (`Right`, 
`RightAnti`, `RightSemi`) or as `Full`, so the rows needing unmatched emission 
are on the operator's probe side. Measured against an unlimited pool (`l` = 200 
rows, `r` = 90 rows, `target_partitions = 1`, `batch_size = 16`, 64-byte pool):
   
   | query | ample | memory-limited (before) |
   | --- | --- | --- |
   | `NOT EXISTS (... l.v > r.w)` | 2 | 0 |
   | `LEFT JOIN ... ON l.v > r.w` | 9336 | 9334 |
   | `FULL JOIN ... ON l.v > r.w` | 9339 | 9337 |
   
   `INNER` and explicit `RIGHT JOIN` were already correct.
   
   This is pre-existing rather than a regression from #24675: that PR only 
changes the spill gate in the same file, and its condition 
(`need_produce_result_in_final(join_type) && right_partition_count > 1`) does 
not cover this path.
   
   ## What changes are included in this PR?
   
   Route that early exit to `EmitGlobalRightUnmatched` instead of `Done` when 
the join tracks unmatched probe-side rows, clearing `right_data` so a fresh 
replay pass is opened. A left side that was empty from the start keeps its 
previous behaviour: no bitmaps have been accumulated, so that state reports 
nothing unmatched and finishes immediately.
   
   Spilling is preserved rather than refused — the fix corrects the emission 
instead of turning these queries into `ResourcesExhausted` errors.
   
   ## Are these changes tested?
   
   Yes, `datafusion/core/tests/memory_limit/nlj_spill_unmatched.rs` adds three 
tests comparing memory-limited results against an ample pool for `LEFT JOIN`, 
`LEFT ANTI`, and `FULL JOIN`. Without the production change they fail with `0 
instead of 2`, `9334 instead of 9336`, and `9337 instead of 9339`; with it they 
pass.
   
   The existing `nested_loop_join` unit tests (46) and the wider `joins` suite 
(1116) pass unchanged, including `test_nlj_memory_limited_right_join`, which 
asserts that a spilling `RIGHT` join still returns its unmatched rows.
   
   ## Are there any user-facing changes?
   
   No API changes. Queries that previously lost rows under a memory limit now 
return the correct result.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to