jayzhan211 commented on code in PR #25840:
URL: https://github.com/apache/datafusion/pull/25840#discussion_r4166675528


##########
datafusion/physical-plan/src/joins/piecewise_merge_join/classic_join.rs:
##########
@@ -631,18 +587,111 @@ fn build_matched_indices_and_mark_buffered(
     )?)
 }
 
-// Creates a record batch from the unmatched indices on the streamed side
-fn create_unmatched_batch(
-    streamed_indices: &mut PrimitiveBuilder<UInt32Type>,
-    stream_batch: &SortedStreamBatch,
+// The last key of the sorted buffered side, or `None` when it is empty or 
every key is NULL:
+// NULLs sort first, so the last key is NULL only when every buffered key is.
+fn buffered_extreme(values: &ArrayRef) -> Result<Option<ColumnarValue>> {
+    Ok(match values.len().checked_sub(1) {
+        Some(last) if values.is_valid(last) => Some(ColumnarValue::Scalar(
+            ScalarValue::try_from_array(values, last)?,

Review Comment:
   For `Dictionary(_, Float*)` keys the pre-filter disagrees with the scan on 
`-0.0`. `apply_cmp` normalizes the streamed array (dictionary values included), 
but `normalize_float_zero_scalar` skips `ScalarValue::Dictionary`, so the 
extreme keeps `-0.0` under total order while `JoinKeyComparator` treats it as 
`0.0`. This gives an internal error when the filter lets a row through, and 
wrong rows when it drops a real match. The base commit returns the right 
results.
   
   Repro:
   ```sql
   set datafusion.optimizer.enable_piecewise_merge_join = true;
   CREATE TABLE dl AS SELECT * FROM (VALUES (1, arrow_cast(-0.0, 
'Dictionary(Int32, Float64)')), (2, arrow_cast(0.5, 'Dictionary(Int32, 
Float64)'))) t(lid, lv);
   CREATE TABLE dr AS SELECT * FROM (VALUES (1, arrow_cast(0.0, 
'Dictionary(Int32, Float64)')), (2, arrow_cast(0.25, 'Dictionary(Int32, 
Float64)'))) t(rid, rv);
   SELECT * FROM dl JOIN dr ON dl.lv < dr.rv;
   -- Internal error: PiecewiseMergeJoin: streamed row 1 reached the scan 
without a match
   
   CREATE TABLE dl2 AS SELECT * FROM (VALUES (1, arrow_cast(-1.0, 
'Dictionary(Int32, Float64)')), (2, arrow_cast(-0.0, 'Dictionary(Int32, 
Float64)'))) t(lid, lv);
   CREATE TABLE dr2 AS SELECT * FROM (VALUES (1, arrow_cast(0.0, 
'Dictionary(Int32, Float64)'))) t(rid, rv);
   SELECT * FROM dl2 RIGHT JOIN dr2 ON dl2.lv >= dr2.rv;
   -- got `NULL NULL 1 0`, expected `2 0 1 0` (NLJ and the base commit agree)
   ```
   
   Fix (normalize the extreme before taking it as a scalar):
   ```diff
   +use datafusion_common::utils::normalize_float_zero;
   ```
   ```diff
    fn buffered_extreme(values: &ArrayRef) -> Result<Option<ColumnarValue>> {
        Ok(match values.len().checked_sub(1) {
   -        Some(last) if values.is_valid(last) => Some(ColumnarValue::Scalar(
   -            ScalarValue::try_from_array(values, last)?,
   -        )),
   +        // `apply_cmp` normalizes `-0.0` only in flat float scalars, not 
inside a
   +        // `ScalarValue::Dictionary`, so normalize the key before taking it.
   +        Some(last) if values.is_valid(last) => {
   +            let extreme = normalize_float_zero(&values.slice(last, 1));
   +            Some(ColumnarValue::Scalar(ScalarValue::try_from_array(
   +                &extreme, 0,
   +            )?))
   +        }
            _ => None,
        })
    }
   ```
   
   Cases for `matchable_rows_agrees_with_scan`. They fail on the current head 
(`dictionary_float64_neg_zero_min/full <: streamed row 0`) and pass with the 
fix:
   ```rs
   (
       // `-0.0` as the buffered extreme inside a dictionary: the smallest
       // key for `<`/`<=`, the largest for `>`/`>=`.
       "dictionary_float64_neg_zero_min",
       Arc::new(DictionaryArray::<Int32Type>::new(
           Int32Array::from(vec![0, 1]),
           Arc::new(Float64Array::from(vec![-0.0, 0.5])),
       )),
       Arc::new(DictionaryArray::<Int32Type>::new(
           Int32Array::from(vec![0, 1, 2]),
           Arc::new(Float64Array::from(vec![0.0, -0.0, 0.25])),
       )),
   ),
   (
       "dictionary_float64_neg_zero_max",
       Arc::new(DictionaryArray::<Int32Type>::new(
           Int32Array::from(vec![0, 1]),
           Arc::new(Float64Array::from(vec![-1.0, -0.0])),
       )),
       Arc::new(DictionaryArray::<Int32Type>::new(
           Int32Array::from(vec![0, 1, 2]),
           Arc::new(Float64Array::from(vec![0.0, -0.0, -0.5])),
       )),
   ),
   ```
   
   The root cause, `normalize_float_zero_scalar` not recursing into 
`ScalarValue::Dictionary`, is already on main: `SELECT arrow_cast(0.0, 
'Dictionary(Int32, Float64)') > arrow_cast(-0.0, 'Dictionary(Int32, Float64)')` 
returns `true`. Worth filing on its own; fixing it there would also cover this.



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