@@ -33,7 +33,7 @@ use crate::joins::hash_join::shared_bounds::{
3333 PartitionBounds , PartitionBuildData , SharedBuildAccumulator ,
3434} ;
3535use crate :: joins:: utils:: {
36- OnceFut , equal_rows_arr, get_final_indices_from_shared_bitmap,
36+ OnceFut , equal_rows_arr, get_final_indices_from_shared_bitmap, matchable_join_keys ,
3737} ;
3838use crate :: stream:: EmptyRecordBatchStream ;
3939use crate :: {
@@ -48,6 +48,7 @@ use crate::{
4848} ;
4949
5050use arrow:: array:: { Array , ArrayRef , UInt32Array , UInt64Array } ;
51+ use arrow:: buffer:: NullBuffer ;
5152use arrow:: datatypes:: { Schema , SchemaRef } ;
5253use arrow:: record_batch:: RecordBatch ;
5354use datafusion_common:: {
@@ -156,6 +157,10 @@ pub(super) struct ProcessProbeBatchState {
156157 batch : RecordBatch ,
157158 /// Probe-side on expressions values
158159 values : Vec < ArrayRef > ,
160+ /// Combined validity of the probe-side key columns, set when NULL keys
161+ /// exist and cannot match (`NullEquality::NullEqualsNothing`); NULL rows
162+ /// are skipped during JoinHashMap lookups
163+ valid_keys : Option < NullBuffer > ,
159164 /// Starting offset for JoinHashMap lookups
160165 offset : MapOffset ,
161166 /// Max joined probe-side index from current batch
@@ -394,13 +399,15 @@ pub(super) fn lookup_join_hashmap(
394399 probe_side_values : & [ ArrayRef ] ,
395400 null_equality : NullEquality ,
396401 hashes_buffer : & [ u64 ] ,
402+ valid_keys : Option < & NullBuffer > ,
397403 limit : usize ,
398404 offset : MapOffset ,
399405 probe_indices_buffer : & mut Vec < u32 > ,
400406 build_indices_buffer : & mut Vec < u64 > ,
401407) -> Result < ( UInt64Array , UInt32Array , Option < MapOffset > ) > {
402408 let next_offset = build_hashmap. get_matched_indices_with_limit_offset (
403409 hashes_buffer,
410+ valid_keys,
404411 limit,
405412 offset,
406413 probe_indices_buffer,
@@ -516,7 +523,7 @@ impl HashJoinStream {
516523 join_type : JoinType ,
517524 left_data : & JoinLeftData ,
518525 ) -> HashJoinStreamState {
519- if left_data. map ( ) . is_empty ( )
526+ if left_data. batch ( ) . num_rows ( ) == 0
520527 && join_type. empty_build_side_produces_empty_result ( )
521528 {
522529 HashJoinStreamState :: Completed
@@ -679,6 +686,7 @@ impl HashJoinStream {
679686 // Precalculate hash values for fetched batch
680687 let keys_values = evaluate_expressions_to_arrays ( & self . on_right , & batch) ?;
681688
689+ let mut valid_keys = None ;
682690 if let Map :: HashMap ( _) = self . build_side . try_as_ready ( ) ?. left_data . map ( ) {
683691 self . hashes_buffer . clear ( ) ;
684692 self . hashes_buffer . resize ( batch. num_rows ( ) , 0 ) ;
@@ -687,6 +695,7 @@ impl HashJoinStream {
687695 & self . random_state ,
688696 & mut self . hashes_buffer ,
689697 ) ?;
698+ valid_keys = matchable_join_keys ( & keys_values, self . null_equality ) ;
690699 }
691700
692701 self . join_metrics . input_batches . add ( 1 ) ;
@@ -696,6 +705,7 @@ impl HashJoinStream {
696705 HashJoinStreamState :: ProcessProbeBatch ( ProcessProbeBatchState {
697706 batch,
698707 values : keys_values,
708+ valid_keys,
699709 offset : ( 0 , None ) ,
700710 joined_probe_idx : None ,
701711 } ) ;
@@ -759,14 +769,9 @@ impl HashJoinStream {
759769 }
760770 }
761771
762- // If the build side is empty, this stream only reaches ProcessProbeBatch for
763- // join types whose output still depends on probe rows.
764772 let is_empty = build_side. left_data . map ( ) . is_empty ( ) ;
765773
766774 if is_empty {
767- // Invariant: state_after_build_ready should have already completed
768- // join types whose result is fixed to empty when the build side is empty.
769- debug_assert ! ( !self . join_type. empty_build_side_produces_empty_result( ) ) ;
770775 let result = build_batch_empty_build_side (
771776 & self . schema ,
772777 build_side. left_data . batch ( ) ,
@@ -790,6 +795,7 @@ impl HashJoinStream {
790795 & state. values ,
791796 self . null_equality ,
792797 & self . hashes_buffer ,
798+ state. valid_keys . as_ref ( ) ,
793799 self . batch_size ,
794800 state. offset ,
795801 & mut self . probe_indices_buffer ,
0 commit comments